理解Event Loop、宏任务、微任务

官方文档:The Node.js Event Loop, Timers, and process.nextTick()

什么是Event Loop

在官方文档里这样写到:

The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.
Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes, the kernel tells Node.js so that the appropriate callback may be added to the poll queue to eventually be executed.

也就是说,尽管JavaScript是单线程的,有了Event Loop,才允许Node.js去执行非阻塞的I/O操作,尽可能的把这些操作转移给系统内核。
由于大多数现代内核都是多线程的,它们可以处理在后台执行的多个操作。当某个操作完成了,内核会通知Node.js,以便将对应的回调函数添加到轮询队列(poll),最终执行。

Event Loop 具体解释

I: 当Node.js启动时;
II: 会初始化Event Loop;
III: 并执行脚本,这些脚本可能会调用异步API、计时器,或者调用process.nextTick()
IV: 接着就开始处理Event Loop。
Event Loop直译就是事件循环,在循环中经历了如下几个阶段:

来自官网图
阶段概述
  • timers:执行setTimeout()setInterval()的回调函数;
  • pending callbacks:执行I/O回调,执行不在timers、check、close callbacks执行的所有回调;
  • idle, prepare:此阶段仅内部使用;
  • poll:获取新的I/O事件,执行I/O相关回调,某些情况node会阻塞在这里;
  • check:setImmediate()的回调函数会在此阶段执行;
  • close callbacks:一些close事件的回调函数会在此阶段执行,例:socket.on('close', ...)

example:

...
setTimeout(()=>console.log('fn'), 1000)
...

启动node,初始化Event Loop;
当脚本运行到setTimeout时;
就开始处理Event Loop了;
setTimeout的回调函数放在timers阶段;
当poll队列为空时,event loop会检测计时器,到了1000毫秒时,会经过check阶段回到timers阶段执行计时器的回调函数,打印出:fn。


poll阶段

poll是比较重要的一个阶段,因为上下衔接了Event Loop的各个阶段。
原文说明了poll阶段的两个重要功能:

  1. Calculating how long it should block and poll for I/O, then
  2. Processing events in the poll queue.

可以理解为:

  1. 计算出阻塞时间并轮询,直到计时器时间到了,事件循环回到timers阶段,执行计时器的回调函数;
  2. 然后,处理poll队列里面的回调函数。

当event loop进入到poll阶段,而此时又没有计时器,那么:

  1. poll队列不为空时:
    event loop会遍历并同步执行poll队列的回调函数,直到其队列为空或者达到系统上限。
  2. poll队列为空时:
    I: 若脚本设置了setImmediate()的回调,event loop会结束poll阶段,进入check阶段执行setImmediate()的回调函数
    II: 若没有setImmediate(),event loop会等待回调函数加入poll队列,并马上执行掉。

当poll队列为空时,event loop会检测是否有已经到期的计时器,若存在则按照循环顺序绕回timers阶段执行计时器的回调函数。

那么,当setTimeout和setImmediate同时存在时,会是什么样的执行顺序呢?
example1:

setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'), 0);

以上代码答案是不确定的,多次测试发现,一会是先'setTimeout'后'setImmediate',一会是先'setImmediate'后'setTimeout',因为整个事件循环中没有办法确定是在哪个阶段开始的。

example2:

setTimeout(()=>{
    setImmediate(() => console.log('setImmediate'), 0);
    setTimeout(() => console.log('setTimeout'), 0);
}, 1000)

以上代码答案一定是:setImmediate setTimeout
因为整个事件循环开启后,确定是先在poll阶段等待停留之后,进入check阶段,而check一定会执行setImmediate回调,再绕回timers阶段执行setTimeout回调。


process.nextTick()

尽管process.nextTick()也属于异步API,但它不存在于事件循环中的任何阶段。

nextTick队列一定是在当前操作完成后紧接着处理,无论是在事件循环的哪个阶段。
example:

setTimeout(()=>{
    setTimeout(()=>console.log('fn1'), 0)
    setImmediate(()=>console.log('fn2'))
    process.nextTick(()=>console.log('fn3'))
}, 1000)

结果: fn3 fn2 fn1
分析:整个事件循环开启后,确定是先在poll等待停留之后,在进入check阶段之前执行nextTick回调(fn3),下一步是check阶段执行setImmediate回调(fn2),再绕回timers阶段执行setTimeout回调(fn1)。


宏任务 微任务

Eventloop在Chrome有两个阶段:
宏任务:MacroTask
微任务:MicroTask

当宏任务和微任务同时出现时,一定是先执行微任务再执行宏任务;
当宏任务里面包含微任务时,先执行微任务。

Chrome里面的宏任务微任务:
宏任务:setTimeout
微任务:promise.then(fn)、await也是转化为promise处理

exmaple:

async function async1(){
    console.log(1)
    await async2()
    console.log(2)
}
async function async2(){
    console.log(3)
}

async1()

new Promise(function(resolve){
    console.log(4)
    resolve()
}).then(function(){
    console.log(5)
})
//13425

以上代码分析:

  1. 首先执行async1(),输出:1
await async2()
console.log(2)
//等同于
Promise.resolve(async2()).then(()=>{console.log(2)})

所以执行async2()输出:3,并将()=>{console.log(2)}放入微任务

  1. new Promise里面的function马上执行,输出:4
    并将function(){console.log(5)}放入微任务
  2. 目前已输出:1 3 4
    然后看微任务里面,输出:2 5
  3. 所以最后得到:13425
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339