Nodejs setImmediate API 求科普

Nodejs setImmediate API 求科普

就字面上理解,就是说 setImmediate 是把那个调用放到了队列中去,而 process.nextTick 并没有把调用放到队列中,只是保存在了某个地方,等待调用 process.nextTick 的那个函数结束后,再自动执行。

忽略我…

13 回复

当然可以。以下是一个关于 Node.js 中 setImmediate API 的科普文章。


Node.js setImmediate API 求科普

简介

setImmediate 是 Node.js 中的一个核心 API,用于在当前事件循环的末尾执行一个回调函数。它通常用于处理一些需要稍后执行的任务,但优先级高于 setTimeout

process.nextTick 的区别

  • process.nextTick:这个 API 会在当前操作完成后立即执行回调函数,优先于任何 I/O 操作和其他事件处理。
  • setImmediate:这个 API 则是在当前事件循环结束时执行回调函数,优先于下一次事件循环中的 I/O 操作和其他事件处理。

示例代码

console.log('开始');

process.nextTick(() => {
    console.log('process.nextTick 回调');
});

setImmediate(() => {
    console.log('setImmediate 回调');
});

console.log('结束');

输出结果

运行上述代码,你将看到以下输出:

开始
结束
process.nextTick 回调
setImmediate 回调

解释

  1. console.log('开始');:首先输出 “开始”。
  2. process.nextTick 回调函数被排在队列中,将在当前操作(即 “结束”)之后立即执行。
  3. setImmediate 回调函数则被安排在当前事件循环的末尾执行,因此会在所有 I/O 操作和定时器之后执行。

总结

  • process.nextTick 用于在当前操作完成后立即执行回调。
  • setImmediate 用于在当前事件循环结束时执行回调,优先于下一次事件循环中的 I/O 操作。

通过这两个 API,你可以更灵活地控制代码的执行顺序,以满足不同的需求。


希望这个科普对你有所帮助!如果你有任何进一步的问题,欢迎继续提问。


call stack 和性能呢?

个人认为,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,见我下面的代码~

难道是尾递归优化相似的作用么? 不过只有 IE 部署的 API 真让人觉得怪怪的 https://developer.mozilla.org/en-US/docs/Web/API/window.setImmediate Node 也到 0.10.0 才有这个 API… http://stackoverflow.com/questions/15349733/setimmediate-vs-nexttick

我还不是太说的清楚他们之间的区别,不过我现在看到一篇文章是讲 nextTick 的。里面有提到 call stack 之类的东西。

我认为说的不正确,无论是setImmediate还是process.nextTick的递归调用都不会造成栈溢出,如果是同步的递归,必须保存调用栈,而异步的递归根本不存在调用栈。之所以会发生Maximum call stack size exceeded,因为process.maxTickDepth的缺省值是1000,如果递归调用nextTick只能调用1000次,超过1000就会报这个错,但并不是真正栈溢出,只是想给你一个提示不希望你递归调用nextTick太多次,如果nextTick递归调用,那么其他的回调事件就会等待,会造成event loop饥饿,所以官方推荐用setImmediate作为递归调用,为什么setImediate,nextTick一个造成不饥饿,一个造成饥饿呢?那就要说两者的区别,可以看我的另一个回答。

我这人最大的问题就是看不得格式混乱,好吧,帮你整理了下 第一段:

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控制递归次数

setImmediate 是 Node.js 中的一个内置函数,用于在当前事件循环迭代完成后立即执行某些操作。它的工作原理是将回调函数放入 immediateQueue 队列中,并且这些回调会在当前操作(如 I/O 操作)完成后立即执行,但优先级低于 process.nextTick

process.nextTick 相比,setImmediate 更适合处理延迟执行的任务,而不是需要在当前函数退出之前立即执行的任务。因此,在某些场景下,setImmediate 会比 process.nextTick 更高效,因为它不会阻塞事件循环。

下面是一些示例代码:

// 示例1: 使用 setImmediate 和 process.nextTick
console.log('开始');

process.nextTick(() => {
    console.log('nextTick 回调1');
});

setImmediate(() => {
    console.log('setImmediate 回调');
});

console.log('结束');

/*
 * 输出:
 * 开始
 * 结束
 * nextTick 回调1
 * setImmediate 回调
 */

上面的代码中,process.nextTick 回调在当前操作结束后立即执行,而 setImmediate 回调将在下一次事件循环迭代中执行。

// 示例2: 在 setImmediate 中使用 setTimeout
console.log('开始');

setImmediate(() => {
    console.log('setImmediate 回调');
    setTimeout(() => {
        console.log('setTimeout 回调');
    }, 0);
});

console.log('结束');

/*
 * 输出:
 * 开始
 * 结束
 * setImmediate 回调
 * setTimeout 回调
 */

在这个例子中,setImmediate 的回调先于 setTimeout 执行,但是二者都属于同一事件循环迭代。

回到顶部