setimmediation vs. nextTick

Node.js 0.10版本今天发布,并引入了setImmediateAPI的变化文档建议在进行递归nextTick调用时使用它。

MDN说来看,它看起来非常类似于process.nextTick

什么时候应该使用nextTick,什么时候应该使用setImmediate?

121703 次浏览

如果你想将函数排在已经在事件队列中的任何I/O事件回调之后,请使用setImmediate。使用process.nextTick有效地将函数排在事件队列的头部,以便在当前函数完成后立即执行。

因此,在试图使用递归分解长时间运行的cpu绑定作业的情况下,您现在应该使用setImmediate而不是process.nextTick来排队进行下一次迭代,否则任何I/O事件回调都不会有机会在迭代之间运行。

在回答的评论中,它没有明确地说明nextTick从Macrosemantics转移到Microsemantics。

在节点0.9之前(引入setimmediation时),nextTick在下一个调用堆栈的开始操作。

从节点0.9开始,nextTick操作在现有调用堆栈的末尾,而setimmediation操作在下一个调用堆栈的开始

查看https://github.com/YuzuJS/setImmediate获取工具和详细信息

举个例子:

import fs from 'fs';
import http from 'http';
    

const options = {
host: 'www.stackoverflow.com',
port: 80,
path: '/index.html'
};


describe('deferredExecution', () => {
it('deferredExecution', (done) => {
console.log('Start');
setTimeout(() => console.log('setTimeout 1'), 0);
setImmediate(() => console.log('setImmediate 1'));
process.nextTick(() => console.log('nextTick 1'));
setImmediate(() => console.log('setImmediate 2'));
process.nextTick(() => console.log('nextTick 2'));
http.get(options, () => console.log('network IO'));
fs.readdir(process.cwd(), () => console.log('file system IO 1'));
setImmediate(() => console.log('setImmediate 3'));
process.nextTick(() => console.log('nextTick 3'));
setImmediate(() => console.log('setImmediate 4'));
fs.readdir(process.cwd(), () => console.log('file system IO 2'));
console.log('End');
setTimeout(done, 1500);
});
});

将给出以下输出

Start // synchronous
End // synchronous
nextTick 1 // microtask
nextTick 2 // microtask
nextTick 3 // microtask
setTimeout 1 // macrotask
file system IO 1 // macrotask
file system IO 2 // macrotask
setImmediate 1 // macrotask
setImmediate 2 // macrotask
setImmediate 3 // macrotask
setImmediate 4 // macrotask
network IO // macrotask

我希望这能帮助你理解其中的区别。

更新:

使用process.nextTick()延迟的回调在任何其他I/O之前运行 事件被触发,而使用setimmediation(),执行被排队

js设计模式, by Mario Casciaro(可能是关于node.js/js最好的书)

我想我可以很好地解释这个。由于nextTick是在当前操作结束时调用的,因此递归地调用它可能会阻止事件循环继续进行。setImmediate通过在事件循环的检查阶段触发来解决这个问题,允许事件循环正常继续。

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
└───────────────────────┘

来源:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

请注意,检查阶段紧跟在轮询阶段之后。这是因为轮询阶段和I/O回调是对setImmediate的调用最有可能运行的地方。因此,理想情况下,这些调用中的大多数实际上都是非常立即的,只是不像nextTick那样立即,nextTick在每个操作之后都会检查,技术上存在于事件循环之外。

让我们看一个关于setImmediateprocess.nextTick区别的小例子:

function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
step(iteration + 1); // Recursive call from setImmediate handler.
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
});
}
step(0);

假设我们刚刚运行了这个程序,并逐步执行事件循环的第一次迭代。它将调用迭代为0的step函数。然后它将注册两个处理程序,一个用于setImmediate,另一个用于process.nextTick。然后,我们从setImmediate处理程序中递归调用这个函数,该处理程序将在下一个检查阶段运行。nextTick处理程序将在当前操作结束时运行,中断事件循环,因此即使它是在第二注册的,它实际上也会首先运行。

最后的顺序是:nextTick在当前操作结束时触发,下一个事件循环开始,正常的事件循环阶段执行,setImmediate触发并递归调用step函数重新开始整个过程。当前操作结束,nextTick触发,等等。

上述代码的输出将是:

nextTick iteration: 0
setImmediate iteration: 0
nextTick iteration: 1
setImmediate iteration: 1
nextTick iteration: 2
setImmediate iteration: 2
nextTick iteration: 3
setImmediate iteration: 3
nextTick iteration: 4
setImmediate iteration: 4
nextTick iteration: 5
setImmediate iteration: 5
nextTick iteration: 6
setImmediate iteration: 6
nextTick iteration: 7
setImmediate iteration: 7
nextTick iteration: 8
setImmediate iteration: 8
nextTick iteration: 9
setImmediate iteration: 9

现在让我们把对step的递归调用移到我们的nextTick处理程序中,而不是setImmediate

function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
step(iteration + 1); // Recursive call from nextTick handler.
});
}
step(0);

现在我们已经将对step的递归调用移动到nextTick处理程序中,事情将以不同的顺序进行。事件循环的第一次迭代运行并调用step,注册一个setImmedaite处理程序和一个nextTick处理程序。当前操作结束后,nextTick处理程序触发,它递归调用step,并注册另一个setImmediate处理程序和另一个nextTick处理程序。由于nextTick处理程序在当前操作之后触发,在nextTick处理程序中注册nextTick处理程序将导致第二个处理程序在当前处理程序操作结束后立即运行。nextTick处理程序将继续触发,防止当前事件循环继续。我们将在看到单个setImmediate处理程序触发之前完成所有的nextTick处理程序。

上面代码的输出结果是:

nextTick iteration: 0
nextTick iteration: 1
nextTick iteration: 2
nextTick iteration: 3
nextTick iteration: 4
nextTick iteration: 5
nextTick iteration: 6
nextTick iteration: 7
nextTick iteration: 8
nextTick iteration: 9
setImmediate iteration: 0
setImmediate iteration: 1
setImmediate iteration: 2
setImmediate iteration: 3
setImmediate iteration: 4
setImmediate iteration: 5
setImmediate iteration: 6
setImmediate iteration: 7
setImmediate iteration: 8
setImmediate iteration: 9

注意,如果我们没有中断递归调用并在10次迭代后中止它,那么nextTick调用将继续递归,并且永远不会让事件循环继续到下一阶段。这就是为什么在递归使用时nextTick会成为阻塞,而setImmediate会在下一个事件循环中触发,并且从一个事件循环中设置另一个setImmediate处理程序根本不会中断当前事件循环,允许它继续正常执行事件循环的各个阶段。

希望有帮助!

PS -我同意其他评论者的观点,这两个函数的名称可以很容易地交换,因为nextTick听起来像是要在下一个事件循环中触发,而不是当前循环的结束,并且当前循环的结束比下一个循环的开始更“直接”。哦,好吧,这就是我们得到的API成熟,人们开始依赖现有的接口。

简单来说,process.NextTick()将在事件循环的下一个tick执行。但是,setimmediation基本上有一个单独的阶段,该阶段确保在setimmediation()下注册的回调只会在IO回调和轮询阶段之后被调用。

请参考这个链接以获得更好的解释: https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c < / p >

simplified event loop events

我建议你检查文档部分专门为循环得到更好的理解。摘录如下:

就用户而言,我们有两个类似的调用,但它们的名称令人困惑。

  • process.nextTick()在同一阶段立即触发

  • setimmediation()在
    的后续迭代或'tick'时触发 李事件循环< / p > < / >

本质上,名字应该互换。process.nextTick()比setimmediation()更立即触发,但这是过去的产物,不太可能改变。

这里有一些很好的答案,详细介绍了它们是如何工作的。

只需添加一个回答特定问题的答案:

什么时候应该使用nextTick,什么时候应该使用setImmediate?


始终使用setImmediate


Node.js事件循环,定时器和process.nextTick()文档包括以下内容:

我们建议开发人员在所有情况下都使用setImmediate(),因为它更容易推理(并且它导致代码与更广泛的环境兼容,如浏览器JS)。


在前面的文档中,它警告process.nextTick会导致…

一些糟糕的情况是因为它允许你通过递归调用process.nextTick()来“饿死”你的I/O,它阻止事件循环到达民意调查阶段。

事实证明,process.nextTick甚至可以饿死Promises:

Promise.resolve().then(() => { console.log('this happens LAST'); });


process.nextTick(() => {
console.log('all of these...');
process.nextTick(() => {
console.log('...happen before...');
process.nextTick(() => {
console.log('...the Promise ever...');
process.nextTick(() => {
console.log('...has a chance to resolve');
})
})
})
})

另一方面,setImmediate是“更容易推理”,避免了这些类型的问题:

Promise.resolve().then(() => { console.log('this happens FIRST'); });


setImmediate(() => {
console.log('this happens LAST');
})

因此,除非对process.nextTick的独特行为有特殊需求,否则推荐的方法是“在所有情况下使用setImmediate()”。

永远不要使用process.nextTick()中断cpu密集型操作

你不应该使用process.nextTick()中断这样的工作。这样做将导致一个永远不会清空的微任务队列——您的应用程序将永远被困在同一个阶段! ——托马斯·亨特。使用Node.js的分布式系统 < / p >

事件循环的每个阶段包含几个回调:

One phase of loop event

一旦process.nextTick()被触发,它将始终保持在相同的相位。

例子

const nt_recursive = () => process.nextTick(nt_recursive);
nt_recursive(); // setInterval will never run


const si_recursive = () => setImmediate(si_recursive);
si_recursive(); // setInterval will run


setInterval(() => console.log('hi'), 10);

在本例中,setInterval()表示应用程序执行的一些异步工作,例如响应传入的HTTP请求。

一旦nt_recursive()函数运行,应用程序最终会得到一个永远不会清空的微任务队列,异步工作永远不会被处理。

但是替代版本si_recursive()没有同样的副作用。

在检查阶段中调用setImmediate()会将回调函数添加到下一个事件循环迭代的检查阶段队列,而不是当前阶段的队列。

Case可以使用process.nextTick

使函数处理都异步进行。

function foo(count, callback) {
if (count <= 0) {
return process.nextTick(() => callback(new TypeError('count > 0')));
}
myAsyncOperation(count, callback);
}

——托马斯·亨特。使用Node.js的分布式系统

在这种情况下,使用setImmediate()process.nextTick()都可以;只是要确保你不会意外地引入递归。