浅谈event loop

289 阅读3分钟

由一道面试题引出:

//请写出输出内容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

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

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');


/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

知识点:

在理清楚以下几个知识点后上面问题就迎刃而解了:

1. 宏任务:包括script(整体代码),setTimeout,setInterval,I/O,UI交互,postMessage

2. 微任务:包括promise.then,MutationObserver

3. 执行过程: a) 执行一个宏任务 b)过程中遇到微任务,则添加其到微任务队列 c)当前宏任务执行完之后,开始依次执行所有微任务 d)GUI线程接管开始渲染 e)渲染完成交给js线程重复这个过程

4. async标志的函数里面,只有当await的表达式返回结果后,await以后的代码才会被执行

5. async/await其实是promise的语法糖,所以await以后的代码相当于放入了then里面

解题:

1. 代码开始,宏任务是script

Macro tasks
---------------
|  script     |
---------------

2. 从上往下,首先看到了同步代码log,则输出 script start

3. 遇到settimeout,将其里面的内容加入宏任务队列

Macro tasks
----------------------------------
|  script  |   log 'setTimeout'  |
----------------------------------

4. 运行async1函数,没遇到await之前都是同步代码,输出 async1 start

5. 遇到await,解析成如下形式

Promise.resolve(async2()).then(() => {console.log('async1 end')})

6. async2因为直接返回值无需等待,所以输出 async2

7. 如上改写可以看出那句log被放入了微任务队列

Macro tasks queue
----------------------------------
|  script  |   log 'setTimeout'  |
----------------------------------
Micro tasks queue
-----------------------
|  log 'async1 end'   |
-----------------------

8. 遇到new promise实例化过程,此过程依然是同步的,输出promise1,并把then的内容加入微队列

Macro tasks queue
----------------------------------
|  script  |   log 'setTimeout'  |
----------------------------------
Micro tasks queue
-------------------------------------------
|  log 'async1 end'   |  log 'promise2'   |
-------------------------------------------

9. 输出同步log:script end

10. 此时已经到了script这个宏任务的最后了,开始执行其所有微任务,清空微任务队列,输出async1 endpromise2

Macro tasks queue
----------------------------------
|  script  |   log 'setTimeout'  |
----------------------------------
Micro tasks queue
-------------------------------------------
|                                         |
-------------------------------------------

11. script宏做完,自己出队列

Macro tasks queue
----------------------------------
|       log 'setTimeout'         |
----------------------------------
Micro tasks queue
-------------------------------------------
|                                         |
-------------------------------------------

12. 浏览器开始渲染,渲染完成开始下一个宏任务

13. 输出setTimeout, 清空宏队列,完成!

改版题:

这种题就算知道了解法,也推荐画图,不然很容易晕

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');
// 答案
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
settimeout加入宏队列,promise1在实例化时输出,promise2和async1 end加入微队列
Macro tasks queue                               Micro tasks queue
---------------------------                 ------------------------------
|  script   |  setTimeout  |                |   promise2  |  async1 end  |
---------------------------                 ------------------------------
promise3输出,promise4加入微队列
-------------------------                   ---------------------------------------
|  script |  settimeout  |                  | promise2  | async1 end  |  promise4  |
-------------------------                   ---------------------------------------

另一个

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
	setTimeout(function() {
		console.log('setTimeout2')
	},0)
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout3');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
// 答案
script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1
settimeout3加入宏队列,settimeout2也加入宏,注意此时后面整体代码加入微队列
Macro tasks queue                               Micro tasks queue
-------------------------------------------      ------------------------------
|  script   |  setTimeout3  |  settimeout2 |     |  setTiemout(settimout1)   |
-------------------------------------------      ------------------------------
promise3输出,promise4加入微队列
-----------------------------------------       -----------------------------------------
|  script |  settimeout3  |  settimeout2 |      | setTimeout(settimeout1) |  promise2  |
-----------------------------------------       -----------------------------------------
script end输出后开始执行微队列,setTimoue被取出执行,把settimeout1加入宏队列
--------------------------------------------------      --------------
| script | setimeout3 | settimeou2 | settimeout1 |      |  promise2  |
--------------------------------------------------       -------------

另一个

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')
    })
})
console.log('script end')
// 答案
script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout

这跟上面差不多,只不过把promise2分开写,实例化是同步的所以先运行log promise2


原文:

github.com/Advanced-Fr…