javascript setImmediate与nextTick

cgfeq70w  于 2022-12-25  发布在  Java
关注(0)|答案(8)|浏览(112)

今天发布了Node.js 0.10版本,引入了setImmediateAPI changes文档建议在进行递归nextTick调用时使用它。
从什么MDN says似乎非常类似于process.nextTick
什么时候应该使用nextTick,什么时候应该使用setImmediate

mepcadol

mepcadol1#

如果要将函数排在事件队列中已存在的任何I/O事件回调之后,请使用setImmediate。使用process.nextTick可有效地将函数排在事件队列的头部,以便在当前函数完成后立即执行。
因此,如果您试图使用递归分解一个长时间运行的CPU密集型作业,您现在可能希望使用setImmediate而不是process.nextTick来对下一次迭代进行排队,否则任何I/O事件回调都没有机会在迭代之间运行。

tkqqtvp1

tkqqtvp12#

举例来说:

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事件触发之前运行,而使用setImmediate(),执行将排在队列中已存在的任何I/O事件之后。

    • Node. js设计模式**,作者Mario Casciaro(可能是关于node. js/js的最好的书)
93ze6v8z

93ze6v8z3#

我想我可以很好地说明这一点。由于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);

假设我们刚刚运行了这个程序,正在单步执行事件循环的第一次迭代,它将调用迭代为零的step函数,然后注册两个处理程序,一个用于setImmediate,一个用于process.nextTick,然后我们从setImmediate处理程序递归调用这个函数,该处理程序将在下一个检查阶段运行。nextTick处理程序将在中断事件循环的当前操作结束时运行,因此即使它是第二个注册的,但实际上它将首先运行。
顺序最终为:nextTick在当前操作结束、下一个事件循环开始、正常事件循环阶段执行时触发,setImmediate触发并递归调用step函数以重新启动整个过程。
以上代码的输出为:

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将在下一个事件循环中触发,并从一个won '中设置另一个setImmediate处理程序。t根本不中断当前事件循环,允许其继续正常执行事件循环的阶段。
希望能有所帮助!
PS-我同意其他评论者的观点,这两个函数的名称可以很容易地交换,因为nextTick听起来像是在下一个事件循环中触发,而不是在当前循环的结尾,而且当前循环的结尾比下一个循环的开头更"直接"。

8zzbczxx

8zzbczxx4#

在答案的注解中,它没有明确指出nextTick从宏语义转移到了微语义。
在节点0.9之前(当引入setImmediate时),nextTick在下一调用栈的开始处操作。
从节点0.9开始,nextTick在现有调用堆栈的末尾操作,而setImmediate在下一个调用堆栈的开头
查看https://github.com/YuzuJS/setImmediate以获取工具和详细信息

cnh2zyt3

cnh2zyt35#

这里有一些很好的答案,详细说明了它们是如何工作的。
只是添加一个回答特定问题的选项:
什么时候应该使用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() *"。

yrdbyhpb

yrdbyhpb6#

简单地说,process.NextTick()将在事件循环的下一个节拍执行。然而,setImmediate()基本上有一个单独的阶段,以确保在setImmediate()下注册的回调仅在IO回调和轮询阶段之后被调用。
请参考此链接,了解详细说明:https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c

hpcdzsge

hpcdzsge7#

我建议你查看一下docs部分的循环,以获得更好的理解。一些片段取自那里:
我们有两个电话就用户而言是相似的,但他们的名字是混乱的。

  • 进程. nextTick()在同一阶段立即触发
  • setImmediate()在以下迭代或

事件循环
本质上,名称应该被交换。process.nextTick()比setImmediate()更快地触发,但这是过去的产物,不太可能改变。

lx0bsm1f

lx0bsm1f8#

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

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

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

一旦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);
}

相关问题