是什么导致node.js等到请求完成?

时间:2022-08-23 18:00:24

So I wasted a bunch of time writing some code like this:

所以我浪费了大量时间编写这样的代码:

function processResponse(error, response, body) {
    if (!error && response.statusCode == 200) {
        console.log(body);
    } else {
        console.error(util.inspect(response, false, null));
    }
    waiting = false;
};

request.get(requestOpts.url, processResponse);

console.log("Waiting");
while(waiting) {
    count += 1;
    if(count % 10000000 == 0) {
        console.log(count);
    }
}

I was trying to get node to wait (and not exit) until the reponse came back from the webserver. Turns out, this didint' work, and what did work was doing nothing. Just:

我试图让节点等待(而不是退出),直到响应从网络服务器返回。事实证明,这不起作用,工作无所作为。只是:

request.get(requestOpts.url, processResponse);

How did request keep node from exiting while the callback was pending?

请求如何在回调挂起时保持节点退出?

3 个解决方案

#1


11  

Node always keep track of any pending callbacks and will not exit until that hits zero. This will include all active network connections/requests as well as filesystem IO and subprocess activity. There's nothing special you need to code to get the expected behavior. node will do what you expect in this case by default.

节点始终跟踪任何挂起的回调,并且在达到零之前不会退出。这将包括所有活动的网络连接/请求以及文件系统IO和子进程活动。您无需特殊编码即可获得预期的行为。默认情况下,节点将执行您在此情况下的预期。

#2


7  

TLDR

TLDR

In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the event loop gets stuck on its first tick and the network I/O is never processed.

在OP的代码中,同步while循环防止事件循环到达轮询阶段,因此事件循环卡在其第一个滴答中,并且永远不会处理网络I / O.

Complete answer

完整答案

Node.js's asynchronous programming model is based on a synchronous loop called the event loop. The basic abstraction of the event loop is function scheduling: a function in Node.js is able to schedule other functions (like network request handlers and timers) to run at some point in the future and in response to some event.

Node.js的异步编程模型基于一个称为事件循环的同步循环。事件循环的基本抽象是函数调度:Node.js中的函数能够调度其他函数(如网络请求处理程序和计时器)在将来的某个时刻运行并响应某些事件。

The event loop is basically a continuously running "while loop". During each "tick" of the loop, the Node.js runtime checks to see whether the condition for a scheduled function is met -- if the condition is met (like if a timer has elapsed), the function is executed.

事件循环基本上是一个连续运行的“while循环”。在循环的每个“tick”期间,Node.js运行时检查是否满足调度函数的条件 - 如果满足条件(如果已经过了计时器),则执行该函数。

The event loop processes callbacks in a particular order.

事件循环按特定顺序处理回调。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

The only place for network requests to be processed is during the poll phase; so in order for a network request to be processed by a callback, the event loop must reach the poll phase.

处理网络请求的唯一地方是在轮询阶段;因此,为了通过回调处理网络请求,事件循环必须到达轮询阶段。

Each stage of the event loop is only able to advance after the synchronous functions from the previous stage have finished running -- i.e. they have returned, and the call stack is empty.

事件循环的每个阶段只能在前一阶段的同步函数完成运行之后才进行 - 即它们已经返回,并且调用堆栈为空。

In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the network request handler is never executed.

在来自OP的代码中,同步while循环可防止事件循环到达轮询阶段,因此永远不会执行网络请求处理程序。

Looping while waiting on a network request

在等待网络请求时循环

In order to run code in a loop while we wait on the network request, we need to somehow give the event loop an opportunity to process other callbacks.

为了在我们等待网络请求时在循环中运行代码,我们需要以某种方式为事件循环提供处理其他回调的机会。

We can achieve this by running our loop in a callback scheduled via setTimeout(). setTimeout() is a mechanism for scheduling functions to run during the timers phase of the event loop.

我们可以通过在通过setTimeout()调度的回调中运行循环来实现这一点。 setTimeout()是一种用于调度在事件循环的计时器阶段期间运行的函数的机制。

Each time our loop finishes, the event loop has an opportunity to process new I/O events. If there's no new I/O event, it will move on to the next setTimeout() handler scheduled via loop().

每次循环结束时,事件循环都有机会处理新的I / O事件。如果没有新的I / O事件,它将继续执行通过loop()调度的下一个setTimeout()处理程序。

const request = require('request')

let count = 0
let isFinished = false

request.get('https://www.google.com', (err, res) => (isFinished = true))

console.log('Waiting')
loop()

function loop () {
  setTimeout(
    () => {
      count += 1
      if (isFinished) {
        console.log('done')
        return
      }

      if(count % 10 == 0) {
          console.log(count);
      }

      return loop()
    },
    0
  )
}

In this example, we keep "recursively" calling loop until the network request has finished. Each time the setTimeout() handler (scheduled by loop()) returns, the event loop moves beyond the timers phase and checks for new network I/O (i.e. our request to Google). As soon as the response is ready, our network request handler is called during the poll phase.

在这个例子中,我们保持“递归”调用循环,直到网络请求完成。每次setTimeout()处理程序(由loop()调度)返回时,事件循环都会超出计时器阶段并检查新的网络I / O(即我们对Google的请求)。一旦响应准备就绪,我们的网络请求处理程序将在轮询阶段被调用。

Even though loop() is "recursive", it doesn't increase the size of the call stack. Since each loop iteration is scheduled via setTimeout, the loop() function won't push another loop() call onto the call stack until the setTimeout() handler is run in the next timers phase - at which point the call stack from the previous loop() call will already be cleared.

即使loop()是“递归的”,它也不会增加调用堆栈的大小。由于每个循环迭代都是通过setTimeout调度的,所以loop()函数不会将另一个loop()调用推送到调用堆栈,直到setTimeout()处理程序在下一个定时器阶段运行 - 此时调用堆栈来自前一个loop()调用已经被清除。

#3


2  

This code should be enough:

这段代码应该足够了:

function processResponse(error, response, body) {
    console.log("Request complete");
    if (!error && response.statusCode == 200) {
        console.log(body);
    } else {
        console.error(util.inspect(response, false, null));
    }
};

console.log("Sending a request");
// Just as a test, try passing the URL hardcoded here,
// because your problem might be just a invalid requestOpts object
request.get("http://*.com", processResponse);

One thing that is totally wrong with your previous code is the fact that loop is actually preventing the request to be executed. Node.js has a single event loop thread, and that code is just consuming 100% CPU, not giving room to your request to be executed.

您之前的代码完全错误的一件事是循环实际上阻止了请求的执行。 Node.js有一个单独的事件循环线程,该代码只占用100%的CPU,没有为您的请求提供空间。

However, it that code above does not work as expected, it might be related to the request library that you are using. If you are using request-promise instead of request, you need to use a different syntax.

但是,上面的代码不能按预期工作,它可能与您正在使用的请求库有关。如果您使用请求承诺而不是请求,则需要使用不同的语法。

#1


11  

Node always keep track of any pending callbacks and will not exit until that hits zero. This will include all active network connections/requests as well as filesystem IO and subprocess activity. There's nothing special you need to code to get the expected behavior. node will do what you expect in this case by default.

节点始终跟踪任何挂起的回调,并且在达到零之前不会退出。这将包括所有活动的网络连接/请求以及文件系统IO和子进程活动。您无需特殊编码即可获得预期的行为。默认情况下,节点将执行您在此情况下的预期。

#2


7  

TLDR

TLDR

In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the event loop gets stuck on its first tick and the network I/O is never processed.

在OP的代码中,同步while循环防止事件循环到达轮询阶段,因此事件循环卡在其第一个滴答中,并且永远不会处理网络I / O.

Complete answer

完整答案

Node.js's asynchronous programming model is based on a synchronous loop called the event loop. The basic abstraction of the event loop is function scheduling: a function in Node.js is able to schedule other functions (like network request handlers and timers) to run at some point in the future and in response to some event.

Node.js的异步编程模型基于一个称为事件循环的同步循环。事件循环的基本抽象是函数调度:Node.js中的函数能够调度其他函数(如网络请求处理程序和计时器)在将来的某个时刻运行并响应某些事件。

The event loop is basically a continuously running "while loop". During each "tick" of the loop, the Node.js runtime checks to see whether the condition for a scheduled function is met -- if the condition is met (like if a timer has elapsed), the function is executed.

事件循环基本上是一个连续运行的“while循环”。在循环的每个“tick”期间,Node.js运行时检查是否满足调度函数的条件 - 如果满足条件(如果已经过了计时器),则执行该函数。

The event loop processes callbacks in a particular order.

事件循环按特定顺序处理回调。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

The only place for network requests to be processed is during the poll phase; so in order for a network request to be processed by a callback, the event loop must reach the poll phase.

处理网络请求的唯一地方是在轮询阶段;因此,为了通过回调处理网络请求,事件循环必须到达轮询阶段。

Each stage of the event loop is only able to advance after the synchronous functions from the previous stage have finished running -- i.e. they have returned, and the call stack is empty.

事件循环的每个阶段只能在前一阶段的同步函数完成运行之后才进行 - 即它们已经返回,并且调用堆栈为空。

In the code from the OP, the synchronous while-loop prevents the event loop from ever reaching the poll phase, so the network request handler is never executed.

在来自OP的代码中,同步while循环可防止事件循环到达轮询阶段,因此永远不会执行网络请求处理程序。

Looping while waiting on a network request

在等待网络请求时循环

In order to run code in a loop while we wait on the network request, we need to somehow give the event loop an opportunity to process other callbacks.

为了在我们等待网络请求时在循环中运行代码,我们需要以某种方式为事件循环提供处理其他回调的机会。

We can achieve this by running our loop in a callback scheduled via setTimeout(). setTimeout() is a mechanism for scheduling functions to run during the timers phase of the event loop.

我们可以通过在通过setTimeout()调度的回调中运行循环来实现这一点。 setTimeout()是一种用于调度在事件循环的计时器阶段期间运行的函数的机制。

Each time our loop finishes, the event loop has an opportunity to process new I/O events. If there's no new I/O event, it will move on to the next setTimeout() handler scheduled via loop().

每次循环结束时,事件循环都有机会处理新的I / O事件。如果没有新的I / O事件,它将继续执行通过loop()调度的下一个setTimeout()处理程序。

const request = require('request')

let count = 0
let isFinished = false

request.get('https://www.google.com', (err, res) => (isFinished = true))

console.log('Waiting')
loop()

function loop () {
  setTimeout(
    () => {
      count += 1
      if (isFinished) {
        console.log('done')
        return
      }

      if(count % 10 == 0) {
          console.log(count);
      }

      return loop()
    },
    0
  )
}

In this example, we keep "recursively" calling loop until the network request has finished. Each time the setTimeout() handler (scheduled by loop()) returns, the event loop moves beyond the timers phase and checks for new network I/O (i.e. our request to Google). As soon as the response is ready, our network request handler is called during the poll phase.

在这个例子中,我们保持“递归”调用循环,直到网络请求完成。每次setTimeout()处理程序(由loop()调度)返回时,事件循环都会超出计时器阶段并检查新的网络I / O(即我们对Google的请求)。一旦响应准备就绪,我们的网络请求处理程序将在轮询阶段被调用。

Even though loop() is "recursive", it doesn't increase the size of the call stack. Since each loop iteration is scheduled via setTimeout, the loop() function won't push another loop() call onto the call stack until the setTimeout() handler is run in the next timers phase - at which point the call stack from the previous loop() call will already be cleared.

即使loop()是“递归的”,它也不会增加调用堆栈的大小。由于每个循环迭代都是通过setTimeout调度的,所以loop()函数不会将另一个loop()调用推送到调用堆栈,直到setTimeout()处理程序在下一个定时器阶段运行 - 此时调用堆栈来自前一个loop()调用已经被清除。

#3


2  

This code should be enough:

这段代码应该足够了:

function processResponse(error, response, body) {
    console.log("Request complete");
    if (!error && response.statusCode == 200) {
        console.log(body);
    } else {
        console.error(util.inspect(response, false, null));
    }
};

console.log("Sending a request");
// Just as a test, try passing the URL hardcoded here,
// because your problem might be just a invalid requestOpts object
request.get("http://*.com", processResponse);

One thing that is totally wrong with your previous code is the fact that loop is actually preventing the request to be executed. Node.js has a single event loop thread, and that code is just consuming 100% CPU, not giving room to your request to be executed.

您之前的代码完全错误的一件事是循环实际上阻止了请求的执行。 Node.js有一个单独的事件循环线程,该代码只占用100%的CPU,没有为您的请求提供空间。

However, it that code above does not work as expected, it might be related to the request library that you are using. If you are using request-promise instead of request, you need to use a different syntax.

但是,上面的代码不能按预期工作,它可能与您正在使用的请求库有关。如果您使用请求承诺而不是请求,则需要使用不同的语法。