理解 Node.js 里的 process.nextTick() 已翻译 100%

合肥华仔 投递于 2013/06/24 11:34 (共 5 段, 翻译完成于 06-26)
阅读 17250
收藏 22
7
加载中

有很多人对Node.js里process.nextTick()的用法感到不理解,下面我们就来看一下process.nextTick()到底是什么,该如何使用。

Node.js是单线程的,除了系统IO之外,在它的事件轮询过程中,同一时间只会处理一个事件。你可以把事件轮询想象成一个大的队列,在每个时间点上,系统只会处理一个事件。即使你的电脑有多个CPU核心,你也无法同时并行的处理多个事件。但也就是这种特性使得node.js适合处理I/O型的应用,不适合那种CPU运算型的应用。在每个I/O型的应用中,你只需要给每一个输入输出定义一个回调函数即可,他们会自动加入到事件轮询的处理队列里。当I/O操作完成后,这个回调函数会被触发。然后系统会继续处理其他的请求。

Andy
Andy
翻译于 2013/06/26 10:17
7

在这种处理模式下,process.nextTick()的意思就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。我们来看一个例子。例子中有一个foo(),你想在下一个时间点上调用他,可以这么做:

function foo() {
    console.error('foo');
}

process.nextTick(foo);
console.error('bar');
运行上面的代码,你从下面终端打印的信息会看到,"bar"的输出在“foo”的前面。这就验证了上面的说法,foo()是在下一个时间点运行的。
bar
foo 
你也可以使用setTimeout()函数来达到貌似同样的执行效果:
setTimeout(foo, 0);
console.log('bar');
但在内部的处理机制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一个单纯的延时,他有更多的 特性

更精确的说,process.nextTick()定义的调用会创建一个新的子堆栈。在当前的栈里,你可以执行任意多的操作。但一旦调用netxTick,函数就必须返回到父堆栈。然后事件轮询机制又重新等待处理新的事件,如果发现nextTick的调用,就会创建一个新的栈。

下面我们来看看,什么情况下使用process.nextTick():

Andy
Andy
翻译于 2013/06/26 10:35
7

在多个事件里交叉执行CPU运算密集型的任务:

在下面的例子里有一个compute(),我们希望这个函数尽可能持续的执行,来进行一些运算密集的任务。

但与此同时,我们还希望系统不要被这个函数堵塞住,还需要能响应处理别的事件。这个应用模式就像一个单线程的web服务server。在这里我们就可以使用process.nextTick()来交叉执行compute()和正常的事件响应。

var http = require('http');

function compute() {
    // performs complicated calculations continuously
    // ...
    process.nextTick(compute);
}

http.createServer(function(req, res) {
     res.writeHead(200, {'Content-Type': 'text/plain'});
     res.end('Hello World');
}).listen(5000, '127.0.0.1');

compute();

在这种模式下,我们不需要递归的调用compute(),我们只需要在事件循环中使用process.nextTick()定义compute()在下一个时间点执行即可。在这个过程中,如果有新的http请求进来,事件循环机制会先处理新的请求,然后再调用compute()。反之,如果你把compute()放在一个递归调用里,那系统就会一直阻塞在compute()里,无法处理新的http请求了。你可以自己试试。

当然,我们无法通过process.nextTick()来获得多CPU下并行执行的真正好处,这只是模拟同一个应用在CPU上分段执行而已。

Andy
Andy
翻译于 2013/06/26 10:59
5

保持回调函数异步执行的原则

当你给一个函数定义一个回调函数时,你要确保这个回调是被异步执行的。下面我们看一个例子,例子中的回调违反了这一原则:

function asyncFake(data, callback) {        
    if(data === 'foo') callback(true);
    else callback(false);
}

asyncFake('bar', function(result) {
    // this callback is actually called synchronously!
});
为什么这样不好呢?我们来看Node.js 文档里一段代码:
var client = net.connect(8124, function() { 
    console.log('client connected');
    client.write('world!\r\n');
});

在上面的代码里,如果因为某种原因,net.connect()变成同步执行的了,回调函数就会被立刻执行,因此回调函数写到客户端的变量就永远不会被初始化了。

这种情况下我们就可以使用process.nextTick()把上面asyncFake()改成异步执行的:

function asyncReal(data, callback) {
    process.nextTick(function() {
        callback(data === 'foo');       
    });
}
Andy
Andy
翻译于 2013/06/26 11:09
4

用在事件触发过程中

来看一个例子,你想写一个库实现这样的功能:从源文件里读取数据,当读取完毕后,触发一个事件同时传递读取的数据。可能你会这样写:

var EventEmitter = require('events').EventEmitter;

function StreamLibrary(resourceName) { 
    this.emit('start');

    // read from the file, and for every chunk read, do:        
    this.emit('data', chunkRead);       
}
StreamLibrary.prototype.__proto__ = EventEmitter.prototype;   // inherit from EventEmitter
下面是一段调用这个库的客户端程序,我们想在程序中监听这些事件:


var stream = new StreamLibrary('fooResource');

stream.on('start', function() {
    console.log('Reading has started');
});

stream.on('data', function(chunk) {
    console.log('Received: ' + chunk);
});

但是上面的代码中,将永远接收不到“start”事件,因为在这个库实例化的时候,“start”事件会被立刻触发执行,但此时事件的回调函数还没有准备好,所以在客户端根本无法接收到这个事件。同样,我们可以用process.nextTick()来改写事件触发的过程,下面是一个正确的版本:

function StreamLibrary(resourceName) {      
    var self = this;

    process.nextTick(function() {
        self.emit('start');
    });

    // read from the file, and for every chunk read, do:        
    this.emit('data', chunkRead);       
}
这就是process.nextTick()的基本用法,如果还有疑问,可以留言讨论。
Andy
Andy
翻译于 2013/06/26 11:59
4
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

orangebook
orangebook

引用来自“lanzhoulei”的评论

测试那个 compute 例子,服务器cmd 一直打印“compute。。。。”,无法处理http请求,为什么?

引用来自“lanzhoulei”的评论

var http = require('http');
function compute() {
console.log("computing....");
process.nextTick(compute);
}

http.createServer(function (request, response) {
console.log("new request...");
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('hello world');
}).listen(5000, '127.0.0.1');

compute();
事件被阻塞了,详细可以看看node.js对process.nextticket函数的说明中解释了该功能相当于while(true) loop
l
lanzhoulei

引用来自“lanzhoulei”的评论

测试那个 compute 例子,服务器cmd 一直打印“compute。。。。”,无法处理http请求,为什么?
var http = require('http');
function compute() {
console.log("computing....");
process.nextTick(compute);
}

http.createServer(function (request, response) {
console.log("new request...");
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('hello world');
}).listen(5000, '127.0.0.1');

compute();
l
lanzhoulei
测试那个 compute 例子,服务器cmd 一直打印“compute。。。。”,无法处理http请求,为什么?
allenwu1018
allenwu1018
4楼正解。

In the above case, if for some reason,net.connect()were to become synchronous, the callback would be called immediately, and hence the client variable will not be initialized when the it's accessed by the callback to write to the client!

应该译为:
在上述例子中,如果因为某些原因,net.connect() 函数是同步的,则回调函数会立即执行,则回调函数在引用 client 变量,向其进行写入时,client 变量还没有被初始化。

原文出处:http://howtonode.org/understanding-process-next-tick
袁晓华
袁晓华
“In the above case, if for some reason,net.connect()were to become synchronous, the callback would be called immediately, and hence theclientvariable will not be initialized when the it's accessed by the callback to write to the client!”
“在上面的代码里,如果因为某种原因,net.connect()变成同步执行的了,回调函数就会被立刻执行,因此回调函数写到客户端的变量就永远不会被初始化了。”

这段的翻译完全不对,意思都弄拧了。
袁晓华
袁晓华
"更精确的说,process.nextTick()定义的调用会创建一个新的子堆栈。在当前的栈里,你可以执行任意多的操作。但一旦调用netxTick,函数就必须返回到父堆栈。然后事件轮询机制又重新等待处理新的事件,如果发现nextTick的调用,就会创建一个新的栈。"
这段的翻译基本是错误的
x
xxgr02

引用来自“teck”的评论

这个data事件应该也是一样的吧?
data是接受数据的时候才触发的啊
teck
teck
这个data事件应该也是一样的吧?
返回顶部
顶部