Node.js 里的 process.nextTick(),简单理解
<div class="text-content"><p>有很多人对Node.js里process.nextTick()的用法感到不理解,下面我们就来看一下process.nextTick()到底是什么,该如何使用。</p>
<p>Node.js是单线程的,除了系统IO之外,在它的事件轮询过程中,同一时间只会处理一个事件。你可以把事件轮询想象成一个大的队列,在每个<strong>时间点</strong>上,系统只会处理一个事件。即使你的电脑有多个CPU核心,你也无法同时并行的处理多个事件。但也就是这种特性使得node.js适合处理I/O型的应用,不适合那种CPU运算型的应用。在每个I/O型的应用中,你只需要给每一个输入输出定义一个回调函数即可,他们会自动加入到事件轮询的处理队列里。当I/O操作完成后,这个回调函数会被触发。然后系统会继续处理其他的请求。</p>
<p>在这种处理模式下,process.nextTick()的意思就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。我们来看一个例子。例子中有一个foo(),你想在下一个时间点上调用他,可以这么做:</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">foo(<span class="hljs-params">) {
<span class="hljs-built_in">console.error(<span class="hljs-string">'foo');
}
process.nextTick(foo);
<span class="hljs-built_in">console.error(<span class="hljs-string">'bar');</span></span></span></span></span></span></span></span></pre>
运行上面的代码,你从下面终端打印的信息会看到,"bar"的输出在“foo”的前面。这就验证了上面的说法,foo()是在下一个时间点运行的。
<pre class="brush:js; toolbar: true; auto-links: false; hljs properties"><span class="hljs-attr">bar
<span class="hljs-attr">foo </span></span></pre>
你也可以使用setTimeout()函数来达到貌似同样的执行效果:
<pre class="brush:js; toolbar: true; auto-links: false; hljs less"><span class="hljs-selector-tag">setTimeout(foo, <span class="hljs-number">0);
<span class="hljs-selector-tag">console<span class="hljs-selector-class">.log(<span class="hljs-string">'bar');</span></span></span></span></span></pre>
但在内部的处理机制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一个单纯的延时,他有更多的 特性。
<p>更精确的说,process.nextTick()定义的调用会创建一个新的子堆栈。在当前的栈里,你可以执行任意多的操作。但一旦调用netxTick,函数就必须返回到父堆栈。然后事件轮询机制又重新等待处理新的事件,如果发现nextTick的调用,就会创建一个新的栈。</p>
<p>下面我们来看看,什么情况下使用process.nextTick():</p>
<p><strong>在多个事件里交叉执行CPU运算密集型的任务:</strong></p>
<p>在下面的例子里有一个compute(),我们希望这个函数尽可能持续的执行,来进行一些运算密集的任务。</p>
<p>但与此同时,我们还希望系统不要被这个函数堵塞住,还需要能响应处理别的事件。这个应用模式就像一个单线程的web服务server。在这里我们就可以使用process.nextTick()来交叉执行compute()和正常的事件响应。</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-keyword">var http = <span class="hljs-built_in">require(<span class="hljs-string">'http');
<span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">compute(<span class="hljs-params">) {
<span class="hljs-comment">// performs complicated calculations continuously
<span class="hljs-comment">// ...
process.nextTick(compute);
}
http.createServer(<span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">req, res) {
res.writeHead(<span class="hljs-number">200, {<span class="hljs-string">'Content-Type': <span class="hljs-string">'text/plain'});
res.end(<span class="hljs-string">'Hello World');
}).listen(<span class="hljs-number">5000, <span class="hljs-string">'127.0.0.1');
compute();</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p>在这种模式下,我们不需要递归的调用compute(),我们只需要在事件循环中使用process.nextTick()定义compute()在下一个时间点执行即可。在这个过程中,如果有新的http请求进来,事件循环机制会先处理新的请求,然后再调用compute()。反之,如果你把compute()放在一个递归调用里,那系统就会一直阻塞在compute()里,无法处理新的http请求了。你可以自己试试。</p>
<p>当然,我们无法通过process.nextTick()来获得多CPU下并行执行的真正好处,这只是模拟同一个应用在CPU上分段执行而已。</p>
<p><strong>保持回调函数异步执行的原则</strong></p>
<p>当你给一个函数定义一个回调函数时,你要确保这个回调是被异步执行的。下面我们看一个例子,例子中的回调违反了这一原则:</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">asyncFake(<span class="hljs-params">data, callback) {
<span class="hljs-keyword">if(data === <span class="hljs-string">'foo') callback(<span class="hljs-literal">true);
<span class="hljs-keyword">else callback(<span class="hljs-literal">false);
}
asyncFake(<span class="hljs-string">'bar', <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">result) {
<span class="hljs-comment">// this callback is actually called synchronously!
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
为什么这样不好呢?我们来看Node.js 文档里一段代码:
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-keyword">var client = net.connect(<span class="hljs-number">8124, <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">) {
<span class="hljs-built_in">console.log(<span class="hljs-string">'client connected');
client.write(<span class="hljs-string">'world!\r\n');
});</span></span></span></span></span></span></span></span></pre>
<p>在上面的代码里,如果因为某种原因,net.connect()变成同步执行的了,回调函数就会被立刻执行,因此回调函数写到客户端的变量就永远不会被初始化了。</p>
<p>这种情况下我们就可以使用process.nextTick()把上面asyncFake()改成异步执行的:</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">asyncReal(<span class="hljs-params">data, callback) {
process.nextTick(<span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">) {
callback(data === <span class="hljs-string">'foo');
});
}</span></span></span></span></span></span></span></span></pre>
<h3><strong>用在事件触发过程中<br></strong></h3>
<p>来看一个例子,你想写一个库实现这样的功能:从源文件里读取数据,当读取完毕后,触发一个事件同时传递读取的数据。可能你会这样写:</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-keyword">var EventEmitter = <span class="hljs-built_in">require(<span class="hljs-string">'events').EventEmitter;
<span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">StreamLibrary(<span class="hljs-params">resourceName) {
<span class="hljs-keyword">this.emit(<span class="hljs-string">'start');
<span class="hljs-comment">// read from the file, and for every chunk read, do:
<span class="hljs-keyword">this.emit(<span class="hljs-string">'data', chunkRead);
}
StreamLibrary.prototype.__proto__ = EventEmitter.prototype; <span class="hljs-comment">// inherit from EventEmitter</span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
下面是一段调用这个库的客户端程序,我们想在程序中监听这些事件:
<p> </p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-keyword">var stream = <span class="hljs-keyword">new StreamLibrary(<span class="hljs-string">'fooResource');
stream.on(<span class="hljs-string">'start', <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">) {
<span class="hljs-built_in">console.log(<span class="hljs-string">'Reading has started');
});
stream.on(<span class="hljs-string">'data', <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">chunk) {
<span class="hljs-built_in">console.log(<span class="hljs-string">'Received: ' + chunk);
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
<p>但是上面的代码中,将永远接收不到“start”事件,因为在这个库实例化的时候,“start”事件会被立刻触发执行,但此时事件的回调函数还没有准备好,所以在客户端根本无法接收到这个事件。同样,我们可以用process.nextTick()来改写事件触发的过程,下面是一个正确的版本:</p>
<pre class="brush:js; toolbar: true; auto-links: false; hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">StreamLibrary(<span class="hljs-params">resourceName) {
<span class="hljs-keyword">var self = <span class="hljs-keyword">this;
process.nextTick(<span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">) {
self.emit(<span class="hljs-string">'start');
});
<span class="hljs-comment">// read from the file, and for every chunk read, do:
<span class="hljs-keyword">this.emit(<span class="hljs-string">'data', chunkRead);
}</span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
这就是process.nextTick()的基本用法,如果还有疑问,可以留言讨论。</div>
<div class="links">
<p>原文地址:http://howtonode.org/understanding-process-next-tick</p>
</div>
</div>
<div id="MySignature" role="contentinfo">
内容仅个人学习使用,不能作为商业用途,一经发现责任自负!<br><br>
来源:https://www.cnblogs.com/lcspring/p/12800629.html
頁:
[1]