js EventLoop(事件循环机制)及执行循序

async/await、promise和setTimeout的执行顺序你真的懂了么?要想弄懂这些事件的执行循序,要先从js EventLoop 事件循环机制说起。

宏任务(macro-task) 微任务(micro-task)
script promise/[then/catch/finally](非new Promise)
setTimeout process.nextTick(node.js环境)
setInterval MutaionOberver(浏览器环境)
setImmediate(node.js环境) Object.observer

事件的执行顺序,是先执行宏任务,然后执行微任务,这个是基础。

  1. 一开始整个脚本作为一个宏任务执行
  2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  3. 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  4. 执行浏览器UI线程的渲染工作
  5. 检查是否有Web Worker任务,有则执行
  6. 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

微任务和宏任务的问题应该是前端面试中比较常见的,他们都从属于异步任务,主要区别在于他们的执行顺序,Event Loop的走向和取值。


image.png

这张图的意思就是:

1)、存在微任务的话,那么就执行所有的微任务

2)、微任务都执行完之后,执行下一个宏任务

3)、1, 2以此循环着

对于微任务的执行顺序上也有些需要注意的地方

基本上,若你喜欢异步任务尽可能快地执行,那就使用process.nextTick

根据语言规格,Promise对象的回调函数,会进入异步任务里面的”微任务“(microtask)队列。微任务队列追加在process.nextTick队列的后面。
所以,下面的代码输出顺序为:

process.nextTick(()=>console.log(1))
Promise.resolve().then(()=>console.log(2))
process.nextTick(()=>console.log(3))
Promise.resolve().then(()=>console.log(4))
//输出为
//1
//3
//2
//4
//上面的代码中,process.nextTick的回调会早执行于Promise

了解了基础之后,那就通过一个实例来练习一下:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

第一轮循环:

  1. 首先打印1;
  2. 接下来是setTimeout是异步任务且是宏任务,加入宏任务暂且记为 setTimeout1;
  3. 接下来是 process 微任务 加入微任务队列 记为 process1;
  4. 接下来是 new Promise 里面直接 resolve(7) 所以打印 7 后面的then是微任务 记为 then1;
  5. 接下来是setTimeout,加入宏任务记为setTimeout2;

这一轮循环结束,打印的结果: 1 7
宏任务队列: setTimeout1、setTimeout2
微任务队列:process1、then1

第二轮循环:

  1. 执行微任务队列;
  2. 执行process1,打印6;
  3. 执行then1,打印8;
  4. 微任务执行完毕后,开始执行宏任务队列;
  5. 执行setTimeout1,先打印2;
  6. 接下来是process微任务,加入微任务队列标记为process2;
  7. 接下来new Promise 里面直接 resolve(4) 所以打印 4,后面的then加入微任务标记为then2;

第二轮循环结束,当前为止打印的结果: 1 7 6 8 2 4
宏任务队列: setTimeout2
微任务队列:process2、then2

第三轮循环:

  1. 执行所有微任务;
  2. 执行process2,打印3;
  3. 执行then2,打印5;
  4. 微任务执行完毕后,开始执行宏任务队列;
  5. 执行setTimeout2,先打印9;
  6. 接下来是process微任务,加入微任务队列标记为process3;
  7. 接下来new Promise 里面直接 resolve(11) 所以打印 11,后面的then加入微任务标记为then3;

第三轮循环结束,当前为止打印的结果: 1 7 6 8 2 4 3 5 9 11
宏任务队列:
微任务队列:process3、then3

第四轮循环:

  1. 执行所有微任务;
  2. 执行process3,打印10;
  3. 执行then3,打印12;

第四轮循环结束,当前为止打印的结果: 1 7 6 8 2 4 3 5 9 11 10 12
此时宏任务和微任务都为空,代码执行结束。

到这里你是不是已经信心满满,原来是这样啊,我已经掌握了。好,那下面就再来一道题来检验一下学习成果吧

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}
console.log('script start')
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
    console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})
promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
Promise.resolve().then(() => {
    console.log('promiseTag')
})
process.nextTick(function() {
    console.log('nextTick');
})
console.log('script end')

正确答案是:script start, a1 start, a2, promise2, script end, nextTick, promise1, a1 end, promise2.then,promiseTag, promise3,setTimeout1
你答对了么?如果答对了那下面就可以忽略了,答错的小伙伴来看一下过程吧:

第一轮循环:

  1. 首先打印 script start;
  2. 然后是setTimeout宏任务,加入宏任务列表,记为setTimeout1;
  3. 接下来是Promise微任务,加入微任务,记为 then1;
  4. 接下来执行a1函数,打印 a1 start,执行a2,打印a2,返回一个promise对象,await让出了线程,把返回的promise加入微任务,记为then2,所以a2后面的代码也要等then2执行完之后才能继续执行;
  5. 接下来定义了promise2,然后执行promise2,new Promise里直接resolve('promise2'),所以打印promise2,promise2的then加入微任务,记为then3;
  6. 接下来执行Promise微任务,加入微任务,记为 then4;
  7. 接下来是process微任务,加入微任务并前置,记为process1;
  8. 接下来打印 script end。

第一轮循环结束,当前打印结果为: script start, a1 start, a2, promise2, script end
宏任务列表:setTimeout1
微任务列表:process1、then1、then2、then3、then4

第二轮循环:

  1. 首先执行微任务列表;
  2. 执行process1, 打印 nextTick;
  3. 执行then1,打印 promise1;
  4. 执行then2,打印 a1 end;
  5. 执行then3, 打印promise2,接下来是Promise微任务,加入当前微任务列表末尾,记为 then5;
  6. 执行then4,打印promiseTag;
  7. 执行then5,打印promise3;
  8. 然后执行宏任务;
  9. 执行setTimeout1,打印setTimeout1。

第二轮循环结束,当前打印结果为: script start, a1 start, a2, promise2, script end, nextTick, promise1, a1 end, promise2.then,promiseTag, promise3,setTimeout1
此时宏任务和微任务都为空,代码执行结束。

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

推荐阅读更多精彩内容