引言
node作为服务器的优势就在于适合处理高并发的请求,对于web网站后台这种I/O密集型的后台尤其有优势,其核心就在于node是一个异步非阻塞模型。关于异步,同步,阻塞,非阻塞这些概念,本文不做讨论。
node的单线程模型
js作为单线程语言,有自己的一套运行机制
同步任务运行在主线程上,异步的所有任务都会在另一个队列中等待执行,一旦同步任务执行完毕开始执行异步队列中的任务,此时可以认为将第一个异步队列中的任务移到主线程,一旦再产生异步操作,就会继续往异步队列中添加,以此循环。这就是为什么promise,setTimeout,setInterval,process.nextTick,setImmediate,ajax请求,看起来虽然位于代码的上面部分却没有被按顺序执行。
看一下node的eventloop的机制,也就是node实现异步的架构。
主要的不同点是用LIBUV去将队列中的任务形成一个eventloop,作为下一个循环需要执行的工作。
观察者模式的体现
个人一直认为js的异步回调是一种观察者模式的体现,订阅/发布,网上的说法是有三种观察者
idle观察者:顾名思义,就是早已等在那里的观察者,以后会说到的process.nextTick就属于这类I/O观察者:顾名思义,就是I/O相关观察者,也就是I/O的回调事件,如网络,文件,数据库I/O等
check观察者:顾名思义,就是需要检查的观察者,后面会说到的setTimeout/setInterval就属于这类
优先级idle观察者>I/O观察者>check观察者
但是个人看法setTimeout()和setInterval()可以归为一类观察者,算是timer观察者,setImmediate()是check观察者,至于原因后面回说明
以下是自己对setTimeout(),setImmediate()和process.nextTick()的比较
首先所有讨论均是建立在node的基础上,三个函数也都只比较分析node 中情况,摘自node文档
setTimeout()
callback <Function> The function to call when the timer elapses.
delay <number> The number of milliseconds to wait before calling the callback.Schedules execution of a one-time callback after delay milliseconds. Returns a Timeout for use with clearTimeout().
The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.
这里明确了两点,一个是settimeout的实际执行时间必然晚于设置时间,作精确定时器根本就是违背他的设计意愿的,另外一点就是他的函数回调是timer观察的。
setImmediate()
callback <Function> The function to call at the end of this turn of the Node.js Event Loop
...args <any> Optional arguments to pass when the callback is called.Schedules the "immediate" execution of the callback after I/O events' callbacks. Returns an Immediate for use with clearImmediate().
When multiple calls to setImmediate() are made, the callback functions are queued for execution in the order in which they are created. The entire callback queue is processed every event loop iteration. If an immediate timer is queued from inside an executing callback, that timer will not be triggered until the next event loop iteration.
核心,回调会被立刻放在eventLoop的末尾
process.nextTick()
callback <Function>
...args <any> Additional arguments to pass when invoking the callbackhe process.nextTick() method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
This is not a simple alias to setTimeout(fn, 0). It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.
文档中自己就提到了process.nextTick()并非 setTimeout(fn, 0),他更有效率,并且执行的序列必在下次所有的event loop的最前列。
比较
之后在node文档中有一个比较细致的比较,链接
比较这三个函数,先说process.nextTick(),文档中说了process.nextTick() is not technically part of the event loop,现在很明确了process.nextTick()并不在event loop里,他回调的执行是在事件等待队列之外的,算是优先级最高的插队人员,那它作为最优先执行回调的就没有疑问了,实际的用处就是有一些必须最优先执行的回调,比如网络服务端中,端口的监听应该必须早于其他事件的回调。
setImmediate() vs setTimeout()
这个可能是网上说法最不统一的地方了,说谁先执行的都有,先测了下面的代码
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
结果还真的是随机的,这么不严谨?其实官方对这个是有解释的
The order in which the timers are executed will vary depending on the context in which they are called. If both are called from within the main module, then timing will be bound by the performance of the process (which can be impacted by other applications running on the machine).
However, if you move the two calls within an I/O cycle, the immediate callback is always executed first:
这个教育了我,真理是有范围的,所谓普遍真理是形而上学。所以说setImmediate() vs setTimeout()谁快不能简单的说,必须先讨论使用的地方。
上面有提到setTimeout(fn, 0)效率不高,至于为什么,暂时参照国内普遍的说法 该函数的事件控制,是被维护在红黑树上,那么为了每次去找超时的回调必然是logn的复杂度,而另外两个函数看起来都应该是1的复杂度
总结
- 综上个人倾向于四种观察者的说法
- 至于setImmediate() vs setTimeout() vs process.nextTick(),process.nextTick()最快,也有独特的应用场景。另外两个的调用时间需要判断是否都在主线程中被执行。
- setTimeout(fn, 0)效率偏低。
- tip:node官方建议使用setimmediate(),因为至少应用的范围就可以到浏览器端了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。