setImmediate与nextTick

时间:2013-03-11 22:14:21

标签: javascript node.js setimmediate

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

MDN saysprocess.nextTick非常相似。

我应该何时使用nextTick,何时应该使用setImmediate

8 个答案:

答案 0 :(得分:473)

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

因此,在您尝试使用递归分解长时间运行的CPU绑定作业的情况下,您现在希望使用setImmediate而不是process.nextTick将下一次迭代排队为否则任何I / O事件回调都无法在迭代之间运行。

答案 1 :(得分:48)

作为插图

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('TO1'), 0);
    setImmediate(() => console.log('IM1'));
    process.nextTick(() => console.log('NT1'));
    setImmediate(() => console.log('IM2'));
    process.nextTick(() => console.log('NT2'));
    http.get(options, () => console.log('IO1'));
    fs.readdir(process.cwd(), () => console.log('IO2'));
    setImmediate(() => console.log('IM3'));
    process.nextTick(() => console.log('NT3'));
    setImmediate(() => console.log('IM4'));
    fs.readdir(process.cwd(), () => console.log('IO3'));
    console.log('Done');
    setTimeout(done, 1500);
  });
});

将提供以下输出

Start
Done
NT1
NT2
NT3
TO1
IO2
IO3
IM1
IM2
IM3
IM4
IO1

我希望这有助于理解差异。

答案 2 :(得分:34)

我想我可以很好地说明这一点。由于在当前操作结束时调用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那么直接,它在每次操作之后被检查并且在技术上存在于事件循环之外。

让我们看看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);

让我们说我们刚刚运行这个程序,并且正在逐步完成事件循环的第一次迭代。它将调用迭代为零的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处理程序将继续触发,从而阻止当前事件循环继续。在看到单个nextTick处理程序触发之前,我们将通过所有setImmediate处理程序。

上述代码的输出最终为:

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处理程序不会中断当前事件循环all,允许它继续正常执行事件循环的阶段。

希望有所帮助!

PS - 我同意其他评论者的说法,这两个函数的名称可以轻易交换,因为nextTick听起来好像它会在下一个事件循环中触发,而不是在当前循环结束时触发,并且当前循环的结束更多&#34;立即&#34;而不是下一个循环的开始。哦,那就是我们在API成熟时所获得的,人们开始依赖现有的接口。

答案 3 :(得分:29)

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

在节点0.9之前(当引入setImmediate时),nextTick在下一个callstack开始时运行。

从节点0.9开始,nextTick在现有callstack的末尾运行,而setImmediate在下一个callstack的开头

查看https://github.com/YuzuJS/setImmediate了解工具和详情

答案 4 :(得分:8)

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

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

simplified event loop events

答案 5 :(得分:1)

我建议您查看docs专门用于Loop的部分,以更好地理解。从那里摘录的一些片段:

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

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

  • setImmediate()在
    的以下迭代或“滴答”中触发 事件循环

从本质上讲,名称应互换。 process.nextTick()比setImmediate()触发得更快,但这是过去的产物,不太可能改变。

答案 6 :(得分:1)

永远不要使用 process.nextTick()

中断 CPU 密集型操作 <块引用>

你永远不应该使用 process.nextTick() 来分解这样的工作。这样做会导致一个永远不会清空的微任务队列——你的应用程序将永远困在同一个阶段! -- Thomas Hunter. Distributed Systems with Node.js

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

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() 调用会将回调添加到 next 事件循环迭代的检查阶段队列,而不是当前阶段的队列。

案例可能使用 process.nextTick

使函数处理全部异步。

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

-- Thomas Hunter. Distributed Systems with Node.js

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

答案 7 :(得分:0)

这里有一些很好的答案,详细说明了两者的工作原理。

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

  

何时应使用nextTick,何时应使用setImmediate


始终使用setImmediate


Node.js Event Loop, Timers, and 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() ”。