事件循环
- 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 | process.nextTick(function A() { |
setImmediate与process.nextTick
1 | setImmediate(function A() { |
上面代码中,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。