从一道题目考虑
今日见到一道面试问题,讲的是async/await
、promise
和setTimeout
的执行顺序,题型如下所示:
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
我给出的答案:
script start
async1 start
async2
asnyc1 end // x
promise1
script end
promise2
setTimeOut
正确答案:
script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut
为何promise1
比asnyc1 end
先出来呢?带上这样的疑问,我了解了一下事件循环体制。
js EventLoop 事件循环体制
JavaScript事件分为两种:
宏任务(macro-task) | 微任务(micro-task) |
---|---|
script | promise.[ then/catch/finally ]((非new Promise)) |
setTimeout | process.nextTick(Node.js 自然环境) |
setInterval | MutaionOberver(电脑浏览器自然环境) |
setImmediate(Node.js 自然环境) | Object.observe |
IO实际操作 | x |
UI互动事情 | x |
postMessage | x |
MessageChannel | x |
事件执行顺序,要先实行宏任务,随后实行微任务,这是基本,每日任务能够有同歩目标和异步任务,同步进到主线程,多线程的进入Event Table并申请注册函数公式,异步事件结束后,会把调用函数放进Event Queue中(宏任务和微每日任务是不一样的Event Queue),同歩任务执行结束后,是从Event Queue中载入事情放进主线程实行,调用函数中可能也会包括不一样任务,所以会循环系统实行以上实际操作。
留意:
setTimeOut
并不是直接的将你的回掉函数公式放入上述多线程序列中来,而是计时器的时间也去了之后,把回掉函数公式放进实行多线程序列中来。如果这时这一序列已经有不少每日任务了,那么就排到他的后边。这就解释了为什么setTimeOut
为什么不可以精确的实行问题了。setTimeOut
实行必须满足两个标准:
- 主过程一定要空余状态,假如到时间了,主过程不空余都不会实行你调用函数
- 这一调用函数必须直到插进多线程序列时前面的异步函数都实行完后,才能实行
promise、async/await
最先,new Promise
是同步每日任务,能被放进主过程中去立即执行。而.then()
函数是异步任务会放进多线程序列中来,那什么时候放到多线程序列中去呢?当我们的promise
情况结束时,便会马上放入多线程序列中来到。
带async
关键词的函数公式会回到一个promise
目标,假如没有await
,实施起来相当于一般函数公式;要是没有await
,async
函数公式并没有很强大是否。
await
关键词需在 async
关键词函数的内部结构,await
写在外会出错;await
好似它的词意,便是等待,等候右边的关系式进行。这时的await
会交出进程,堵塞async
内后续编码,先到实行async
以外编码。等外边的同歩执行命令结束,才能实行里边的后续编码。即使await
的并不是promise
目标,是一个同歩函数公式,还会等这样的操作。
流程梳理
大家总体再梳理一下上边执行命令的操作流程:
- 全部代码片段(script)作为一个宏任务实行
console.log('script start')
,导出script start
;- 实行
setTimeout
,是一个多线程姿势,放进宏任务多线程序列中;- 实行
async1()
,导出async1 start
,再次往下实行;- 实行
async2()
,导出async2
,并返回一个promise
目标,await
交出了进程,把返回promise
加入微任务多线程序列,因此async1()
下边的代码也需要等候上边结束后执行;- 实行
new Promise
,导出promise1
,再将resolve()
放进微任务多线程序列;- 实行
console.log('script end')
,导出script end
;- 到这里同步编码就全实行实现了,然后再去微任务多线程序列里去获取每日任务
- 下面实行
resolve
(async2
返回promise
返回),输出了async1 end
;- 随后实行
resolve(new Promise的)
,输出了promise2
;- 最终实行
setTimeout
,输出了settimeout
。
于第4
步中, await
这里有一个体制, 便是 await
漫长的等待, 不容易堵塞外部函数的落实, 而 await
等待 如果是一个 Promise
则 Promise
里边的代码或是同步执行, 要不是 Promise
,便会应用 Promise.resolve
去进行封装形式, 这儿的 async2
是一个 async
方式, 里边的 打印出会同步执行, 而 await async2()
后边的代码 会放进微任务队列里的第一个部位,等候外界同歩执行命令结束之后再执行。
因此我明白了script end
怎么会先于async1 end
导出。