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

boa*_*der 8 javascript node.js

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

request.get(requestOpts.url, processResponse);
Run Code Online (Sandbox Code Playgroud)

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

Pet*_*ons 12

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


bgr*_*ran 9

TLDR

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

完整答案

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

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

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

   ?????????????????????????????
??>?           timers          ?
?  ?????????????????????????????
?  ?????????????????????????????
?  ?     pending callbacks     ?
?  ?????????????????????????????
?  ?????????????????????????????
?  ?       idle, prepare       ?
?  ?????????????????????????????      ?????????????????
?  ?????????????????????????????      ?   incoming:   ?
?  ?           poll            ?<??????  connections, ?
?  ?????????????????????????????      ?   data, etc.  ?
?  ?????????????????????????????      ?????????????????
?  ?           check           ?
?  ?????????????????????????????
?  ?????????????????????????????
????      close callbacks      ?
   ?????????????????????????????
Run Code Online (Sandbox Code Playgroud)

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

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

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

在等待网络请求时循环

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

我们可以通过在通过调度的回调中运行循环来实现这一点setTimeout().setTimeout()是一种调度函数timers在事件循环阶段运行的机制.

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

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
  )
}
Run Code Online (Sandbox Code Playgroud)

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

即使loop()是"递归",它也不会增加调用堆栈的大小.由于每个循环迭代都是通过调度进行的setTimeout,因此该loop()函数不会将另一个loop()调用推送到调用堆栈,直到setTimeout()处理程序在下一个timers阶段运行- 此时前一个loop()调用的调用堆栈将被清除.