您的位置:首页 > Web前端 > Node.js

Node.js 里的 process.nextTick()

2015-05-23 10:41 567 查看
I have seen quite a few people being confused aboutprocess.nextTick(). Let's take a look at whatprocess.nextTick()does, and when to use it.

As you might already know, every Node application runs on a single thread. What this means is that apart from I/O - at any time, only one task/event is processed by Node's event loop. You can imagine this event loop
to be a queue of callbacks that are processed by Node on every tick of the event loop. So, even if you are running Node on a multi-core machine, you will not get any parallelism in terms of actual processing - all
events will be processed only one at a time. This is why Node is a great fit for I/O bound tasks, and definitely not for CPU intensive tasks. For every I/O bound task, you can simply define a callback that will get added to the event queue. The callback will
fire when the I/O operation is done, and in the mean time, the application can continue to process other I/O bound requests.
译者信息


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

Node.js是单线程的,除了系统IO之外,在它的事件轮询过程中,同一时间只会处理一个事件。你可以把事件轮询想象成一个大的队列,在每个时间点上,系统只会处理一个事件。即使你的电脑有多个CPU核心,你也无法同时并行的处理多个事件。但也就是这种特性使得node.js适合处理I/O型的应用,不适合那种CPU运算型的应用。在每个I/O型的应用中,你只需要给每一个输入输出定义一个回调函数即可,他们会自动加入到事件轮询的处理队列里。当I/O操作完成后,这个回调函数会被触发。然后系统会继续处理其他的请求。
Given this model, whatprocess.nextTick()actually does is defer the execution of an action till the next pass around the event loop. Let's take a simple example. If we had a functionfoo()which we wanted to invoke in the next tick, this is how we do it:
function foo() {
console.error('foo');
}

process.nextTick(foo);
console.error('bar');

If you ran the above snippet, you will notice thatbarwill be printed in your console beforefoo, as we have delayed the invokation offoo()till the next tick of the event loop:
bar
foo


In fact, you can get the same result by usingsetTimeout()this way:
setTimeout(foo, 0);
console.log('bar');


However,process.nextTick()is not just a simple alias tosetTimeout(fn, 0)- it's far more efficient.

More precisely,process.nextTick()defers the function until a completely new stack. You can call as many functions as you want in the current stack. The function that called nextTick has to return, as well as its parent, all the way up to the root of the stack.
Then when the event loop is looking for a new event to execute, yournextTick'ed function will be there in the event queue and execute on a whole new stack.

Let's see where we can useprocess.nextTick():
译者信息


在这种处理模式下,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():

Interleaving execution of a CPU intensive task with other events

Let's say we have a taskcompute()which needs to run almost continuously, and does some CPU intensive calculations. If we wanted to also handle other events, like serving HTTP requests in the same Node process, we can useprocess.nextTick()to interleave the execution
ofcompute()with the processing of requests this way:
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();

In this model, instead of callingcompute()recursively, we useprocess.nextTick()to delay the execution ofcompute()till the next tick of the event loop. By doing so, we ensure that if any other HTTP requests are queued in the event loop, they will be processed
before the next timecompute()gets invoked. If we had not usedprocess.nextTick()and had simply calledcompute()recursively, the program would not have been able to process any incoming HTTP requests. Try it for yourself!

So, alas, we don't really get any magical multi-core parallelism benefits by usingprocess.nextTick(), but we can still use it to share CPU usage between different parts of our application.
译者信息


在多个事件里交叉执行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上分段执行而已。

Keeping callbacks truly asynchronous

When you are writing a function that takes a callback, you should always ensure that this callback is fired asynchronously. Let's look at an example which violates this convention:
function asyncFake(data, callback) {
if(data === 'foo') callback(true);
else callback(false);
}

asyncFake('bar', function(result) {
// this callback is actually called synchronously!
});

Why is this inconsistency bad? Let's consider this example taken from Node'sdocumentation:
var client = net.connect(8124, function() {
console.log('client connected');
client.write('world!\r\n');
});

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!

We can correctasyncFake()to be always asynchronous this way:
function asyncReal(data, callback) {
process.nextTick(function() {
callback(data === 'foo');
});
}

译者信息


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

当你给一个函数定义一个回调函数时,你要确保这个回调是被异步执行的。下面我们看一个例子,例子中的回调违反了这一原则:
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');
});
}

When emitting events

Let's say you are writing a library that reads from a source and emits events that contains the chunks that are read. Such a library might look like this:
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

Let's say that somewhere else, someone is listening to these events:
var stream = new StreamLibrary('fooResource');

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

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

In the above example, the listener will never get thestartevent as that event would be emitted byStreamLibraryimmediately during the constructor call. At that time, we have not yet assigned a callback to thestartevent yet. Therefore, we would never catch this
event! Once again, we can useprocess.nextTick()to defer theemittill the listener has had the chance to listen for the event.
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);
}

I hope that demystifiesprocess.nextTick(). If I have missed out something, please do share in the comments.

译者信息


用在事件触发过程中

来看一个例子,你想写一个库实现这样的功能:从源文件里读取数据,当读取完毕后,触发一个事件同时传递读取的数据。可能你会这样写:
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()的基本用法,如果还有疑问,可以留言讨论。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: