7

我正在研究 node.js 并在 node.js 手册中遇到了这个例子:

...
var req = http.request(options);
req.end();

req.on('upgrade', function(res, socket, upgradeHead) {
  console.log('got upgraded!');
  socket.end();
  process.exit(0);
});
...

我在这个例子中看到的是附加到 HTTP 请求事件的处理程序,创建请求之后,甚至在它(预定)发送之后。更糟糕的是,手册说:

如果未侦听此事件,则接收升级标头的客户端将关闭其连接。

在有机会附加处理程序之前 ,事件是否可能发生?req.on(...我怀疑我不了解节点的异步模型中的某些内容。还是节点手册中的这段代码旨在希望网络请求比执行下一行代码花费更长的时间?!

另一个例子:

http.get("http://www.google.com/index.html", function(res) {
  console.log("Got response: " + res.statusCode);
}).on('error', function(e) {
  console.log("Got error: " + e.message);
});

在这里,HTTP 请求将在对象创建后立即启动,并且我们仅在之后附加一个错误处理程序。同样,(1)它是仅由于网络延迟而起作用的代码,(2)我没有得到有关 node.js 概念的信息,或者(2b)事件将“等待”直到我将处理程序附加到它?

编辑:更好的例子,也来自手册。下面的好例子和例子是不同的,只是因为在好的例子中,我们足够快地附加事件,因此丢失数据的机会很低,或者永远不可能以这种方式丢失数据(为什么?!)

// Good
request.on('response', function (response) {
  response.on('data', function (chunk) {
    console.log('BODY: ' + chunk);
  });
});

// Bad - misses all or part of the body
request.on('response', function (response) {
  setTimeout(function () {
    response.on('data', function (chunk) {
      console.log('BODY: ' + chunk);
    });
  }, 10);
});
4

2 回答 2

6

你错过的是 JavaScript 根本不是异步的!我的意思是 JavaScript 是单线程的,异步操作实际上不是异步的。有一个非常奇特的队列模型,它给人一种 JavaScript 是异步的错觉(不要误会我的意思:它仍然最有效的模型)。

那么这是什么意思呢?这意味着一旦同步代码运行,其他代码就不可能并行运行。所以例如在这段代码中

var req = http.request(options);
req.end();
req.on(...);

请求已调度,但主线程(即操作)尚未在req.end(). 只要主要操作还没有完成,异步代码就不能在两者之间触发。特别是处理程序总是在实际事件有机会发生之前设置。

再举一个例子,让它更清楚一点。考虑这段代码:

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
    while(true) { } // <------ infinite loop doing nothing
}).listen(1337, '127.0.0.1');

请注意,第一个请求将成功完成。但是任何其他请求都不会得到响应。这是因为循环永远不会完成操作,因此 JavaScript无法跳转到另一个事件。此代码使应用程序永久崩溃,超出任何希望。所以要小心使用 Node.js 的同步代码。:)

于 2012-07-23T14:36:08.490 回答
3

要理解的关键是只有一个事件循环,并且当您调用异步函数时,控件只会离开事件循环的当前“滴答”。通常,当您执行 I/O(每几行代码很常见)或调用setTimeout/setInterval(相当罕见)。因此,只要您的所有事件处理程序都在事件循环的同一滴答声中注册,您就永远不会丢失任何数据。此外,在事件循环的那个滴答声中,附加处理程序的顺序无关紧要,因为在那个滴答声期间,您的代码实际上是节点正在执行的唯一事情,因此没有其他代码可以接收 I/O 或调用任何事件处理程序,直到当前事件循环滴答完成。这与等待“足够长”或网络延迟或类似的东西无关。单个事件循环的简单性保证了事件处理函数的可预测操作。

于 2012-07-23T14:43:19.773 回答