看到 @PeakJi](http://weibo.com/peakji) 一条微博
node.js 0.9之后,任何异步递归都应用setImmediate而非process.nextTick!setImmediate和process.nextTick的区别是:前者将defer到队列末,且不会生成call stack;而后者是defer到该函数结束后执行,且process.nextTick用于递归会报警并最后爆盏…至于setInterval(func,0)就别用了,那是浏览器技巧。
地址: http://weibo.com/1811148004/zxJaEh6jK
觉得这个 API 好陌生… 搜索到的东西不多… https://github.com/NobleJS/setImmediate 可以细化一下么?
就字面上理解,就是说 setImmediate 是把那个调用放到了队列中去,而 process.nextTick 并没有把调用放到队列中,只是保存在了某个地方,等待调用 process.nextTick 的那个函数结束后,再自动执行。
忽略我…
@jiyinyiyong 无能为力…
个人认为,process.nextTick 是一种插队行为,一种藐视规则的行为,应加以谴责。性能上我认为没有本质区别。
这里说的setImmediate不是指Github上那个setImmediate.js,而是一个新的API。 你可以在最新版本的node下试试如下的代码:
function recurse(i,end){
if(i>end)
{
console.log('Done!');
}
else
{
console.log(i);
process.nextTick(recurse(i+1,end));
}
}
recurse(0,99999999);
执行几次后马上就 RangeError: Maximum call stack size exceeded
然后换用setImmediate:
function recurse(i,end){
if(i>end)
{
console.log('Done!');
}
else
{
console.log(i);
setImmediate(recurse,i+1,end);
}
}
recurse(0,99999999);
就完全没问题!
这是因为setImmediate不会生成call stack,异步的递归必须用它。
性能上差不多,关键差别在于是否有call stack,见我下面的代码~
我还不是太说的清楚他们之间的区别,不过我现在看到一篇文章是讲 nextTick 的。里面有提到 call stack 之类的东西。
我认为说的不正确,无论是setImmediate还是process.nextTick的递归调用都不会造成栈溢出,如果是同步的递归,必须保存调用栈,而异步的递归根本不存在调用栈。之所以会发生Maximum call stack size exceeded,因为process.maxTickDepth的缺省值是1000,如果递归调用nextTick只能调用1000次,超过1000就会报这个错,但并不是真正栈溢出,只是想给你一个提示不希望你递归调用nextTick太多次,如果nextTick递归调用,那么其他的回调事件就会等待,会造成event loop饥饿,所以官方推荐用setImmediate作为递归调用,为什么setImediate,nextTick一个造成不饥饿,一个造成饥饿呢?那就要说两者的区别,可以看我的另一个回答。
首先同意@nodehugo所说,nextTick的栈溢出并不是真正的栈溢出,而是人为加上的东西,当初的讨论在这里 https://github.com/joyent/node/pull/3709 和 https://github.com/joyent/node/pull/3723
抛却maxTickDepth的问题,nextTick 和 setImmediate 主要的区别在于任务插入的位置
nextTick 的插入位置是在当前帧的末尾、io回调之前,如果nextTick过多,会导致io回调不断延后 setImmediate 的插入位置是在下一帧,不会影响io回调
我这人最大的问题就是看不得格式混乱,好吧,帮你整理了下 第一段:
function recurse (i, end) {
if (i > end) {
console.log('Done!');
} else {
console.log(i);
process.nextTick(recurse(i+1, end));
}
}
第二段;
function recurse (i, end) {
if (i > end) {
console.log('Done!');
} else {
console.log(i);
setImmediate(recurse, i+1, end);
}
}
可以这样来理解. process.nextTick和setImmediate方法在实现上分别对应这两个队列-- 不妨叫作nextTick队列和immediate队列. 到nextTick的时候, 这两个队列被执行的情况有所差异: nextTick队列中的方法会被全部执行(包括在执行过程中新加的),而immediate队列只会取其第一个方法来执行.
function nextTick(msg, cb) { process.nextTick(function() { console.log('tick: ’ + msg); if (cb) { cb(); } }); }
function immediate(msg, cb) { setImmediate(function() { console.log('immediate : ’ + msg); if (cb) { cb(); } }); }
nextTick(‘1’); nextTick(‘2’, function() { nextTick(‘10’); });
immediate(‘3’, function() { nextTick(‘5’); });
nextTick(‘7’, function() { immediate(‘9’); });
immediate(‘4’, function() { nextTick(‘8’); });
这段代码的输出是:
tick: 1 tick: 2 tick: 7 tick: 10 immediate : 3 tick: 5 immediate : 4 tick: 8 immediate : 9
解释如下:
1). 第一遍执行时, 会分别向nextTick队列和immedidate队列中加入方法,它们变成:
nextTick: 1 2 7 数字代表输出相应数字的那个nextTick方法对应的callback方法,下同
immedidate: 3 4
2). 到了nextTick, 开始执行回调
先执行 nextTick 队列中的回调(全部执行才结束):
2.1) 执行1 – 输出1
, nextTick队列变为 2 7
2.2) 执行 2 – 输出2
, 并向nextTick队列添加10, nextTick队列变为 7, 10
2.3) 执行7 – 输出7
, 并向immediate队列添加9. nextTick 队列变为 10, immediate队列变为 3 4 9
2.4) nextTick 队列中还有一个新添加的10, 故执行它, 输出10
再执行 immediate队列的第一个回调方法(immediate队列为 3 4 9), 即执行3. 3的执行,输出3
同时向nextTick队列中加入5 — 5 不会在这个tick执行, 因为本轮nextTick的执行已经结束了. 此时, 队列变为:
nextTick: 5
immedidate: 4 9
这也是进入下一轮tick前的队列状态.
3). 到了nextTick, 开始执行回调 队列为: nextTick: 5 immedidate: 4 9
和上一轮类似:
3.1) 执行 nextTick 5, 输出 5
. nextTick为空, 执行完毕.
执行immediate队列的第一个回调, 即4, 输出4
, 并向nextTick队列加入8. 此时队列变化为:
nextTick: 8
immediate: 9
4). 到了nextTick, 开始执行回调
根据上述原理,不难知道 将分别执行 nextTick 8 和 immediate 9 , 将输出8
和输出9
.
setImmediate会让步io事件先执行,nextTick则不会。 如果你不清楚它们的区别是什么的时候,你可以不考虑nexttick。
nextTick的递归写法有问题,用process.nextTick(recurse(i+1,end));的话相当于直接执行recurse(i+1,end),也就变成了普通的函数递归,因此会爆栈,如果改为以下写法就没有这个问题,在0.10版本之前不会出现爆栈问题 process.nextTick(function() { return recurse(i+1, end); }); 而在0.10版本后由process.maxTickDepth控制递归次数
@chyingp 的确发生了变化,可以参考最新的文档。
下面是对GuoZhang 的代码的扩展(node v0.12.7):
'use strict';
function nextTick(msg, cb) {
process.nextTick(function() {
console.log('tick: ' + msg);
if (cb) {
cb();
}
});
}
function immediate(msg, cb) {
setImmediate(function() {
console.log('immediate : ' + msg);
if (cb) {
cb();
}
});
}
nextTick('1');
nextTick('2', function() {
nextTick('10');
});
immediate('3', function() {
nextTick('5');
});
nextTick('7', function() {
immediate('9');
});
immediate('4', function() {
nextTick('8');
});
let n = 0;
const timer = setInterval(function() {
n++;
console.log('interval:', n);
nextTick('tick from interval: ' + n);
nextTick('another tick from interval: ' + n);
immediate('immediate from interval: ' + n);
immediate('another immediate from interval: ' + n);
if ( n === 3 ) {
clearInterval(timer);
}
}, 0);
console.log('the last line of the program.');