• new Promise()当中的内容同步执行;
  • process.nextTick执行最快,其次Promise。nextTick会比其他微任务、宏任务执行快
  • 宏任务和微任务:

图主线程的绿色部分,还是表示运行时间,而橙色部分表示空闲时间。每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在红色的等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。

  • 总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。

浏览器

  • 会先执行一个栈以及栈中的微任务,才会走下一个栈;
  • setImmediate会优于setTimeout执行
  • 微任务包括 process.nextTick ,promise ,Object.observe ,MutationObserver
  • 宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

node端

  • node中,会把栈走完,才会走微任务,微任务在两个宏任务中间执行;
  • setImmediate与setTimeout执行顺序根据情况判断;
  • nextTick:每次事件轮询后,在额外的I/O执行前,nexttick队列都会优先执行。 递归调用nextTick callbacks 会阻塞任何I/O操作,就像一个while(true); 循环一样。
    • process.nextTick方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。
  • setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像

事件循环图
来源:
几道高级前端面试题解析
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘

  • timers阶段会执行 setTimeout 和 setInterval

  • poll 阶段: 执行到点的定时器,执行 poll 队列中的事件

    • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制
    • 如果 poll 队列为空
      • 如果有 setImmediate,poll 阶段会停止并且进入到 check 阶段执行 setImmediate
      • 如果没有 setImmediate 需要执行,会等待回调被加入到队列中并立即执行回调
  • check: setImmediate

  • close:执行 close 事件

setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行,setImmediate 设计在check阶段执行;

微任务不在event loop的任何阶段执行,而是在各个阶段切换的中间执行

microtask 会在以上每个阶段完成后立即执行,Node 中的 process.nextTick 会先于其他 microtask 执行。

示例

process.nextTick递归

1
2
3
4
5
6
7
8
9
10
11
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED

setImmediate与process.nextTick

1
2
3
4
5
6
7
8
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);

上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定
Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

我们由此得到了process.nextTick和setImmediate的一个重要区别:

多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!

事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。

参考

浅析nodejs事件循环机制

浏览器和Node.js中的Event Loop

JavaScript 运行机制详解:再谈Event Loop

谈谈 Event Loop(事件循环)机制