async/await、promise和setTimeout

lxf2023-03-15 17:20:02

从一道题目考虑

今日见到一道面试问题,讲的是async/awaitpromisesetTimeout的执行顺序,题型如下所示:

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

为何promise1asnyc1 end先出来呢?带上这样的疑问,我了解了一下事件循环体制

js EventLoop 事件循环体制

JavaScript事件分为两种:

宏任务(macro-task)微任务(micro-task)
scriptpromise.[ then/catch/finally ]((非new Promise))
setTimeoutprocess.nextTick(Node.js 自然环境)
setIntervalMutaionOberver(电脑浏览器自然环境)
setImmediate(Node.js 自然环境)Object.observe
IO实际操作x
UI互动事情x
postMessagex
MessageChannelx

事件执行顺序,要先实行宏任务,随后实行微任务,这是基本,每日任务能够有同歩目标和异步任务,同步进到主线程,多线程的进入Event Table并申请注册函数公式,异步事件结束后,会把调用函数放进Event Queue中(宏任务和微每日任务是不一样的Event Queue),同歩任务执行结束后,是从Event Queue中载入事情放进主线程实行,调用函数中可能也会包括不一样任务,所以会循环系统实行以上实际操作。

留意: setTimeOut并不是直接的将你的回掉函数公式放入上述多线程序列中来,而是计时器的时间也去了之后,把回掉函数公式放进实行多线程序列中来。如果这时这一序列已经有不少每日任务了,那么就排到他的后边。这就解释了为什么setTimeOut为什么不可以精确的实行问题了。setTimeOut实行必须满足两个标准

  1. 主过程一定要空余状态,假如到时间了,主过程不空余都不会实行你调用函数
  2. 这一调用函数必须直到插进多线程序列时前面的异步函数都实行完后,才能实行

promise、async/await

最先,new Promise是同步每日任务,能被放进主过程中去立即执行。而.then()函数是异步任务会放进多线程序列中来,那什么时候放到多线程序列中去呢?当我们的promise情况结束时,便会马上放入多线程序列中来到。

async关键词的函数公式会回到一个promise目标,假如没有await,实施起来相当于一般函数公式;要是没有awaitasync函数公式并没有很强大是否。

await 关键词需在 async 关键词函数的内部结构,await 写在外会出错;await好似它的词意,便是等待,等候右边的关系式进行。这时的await会交出进程,堵塞async内后续编码,先到实行async以外编码。等外边的同歩执行命令结束,才能实行里边的后续编码。即使await的并不是promise目标,是一个同歩函数公式,还会等这样的操作。

流程梳理

大家总体再梳理一下上边执行命令的操作流程:

  1. 全部代码片段(script)作为一个宏任务实行console.log('script start'),导出script start
  2. 实行setTimeout,是一个多线程姿势,放进宏任务多线程序列中;
  3. 实行async1(),导出async1 start,再次往下实行;
  4. 实行async2(),导出async2,并返回一个promise目标,await交出了进程,把返回promise加入微任务多线程序列,因此async1()下边的代码也需要等候上边结束后执行;
  5. 实行 new Promise,导出promise1,再将resolve()放进微任务多线程序列;
  6. 实行console.log('script end'),导出script end
  7. 到这里同步编码就全实行实现了,然后再去微任务多线程序列里去获取每日任务
  8. 下面实行resolveasync2返回promise返回),输出了async1 end
  9. 随后实行resolve(new Promise的),输出了promise2
  10. 最终实行setTimeout,输出了settimeout

于第4步中, await 这里有一个体制, 便是 await 漫长的等待, 不容易堵塞外部函数的落实, 而 await 等待 如果是一个 PromisePromise 里边的代码或是同步执行, 要不是 Promise ,便会应用 Promise.resolve 去进行封装形式, 这儿的 async2 是一个 async 方式, 里边的 打印出会同步执行, 而 await async2() 后边的代码 会放进微任务队列里的第一个部位,等候外界同歩执行命令结束之后再执行。

因此我明白了script end怎么会先于async1 end导出。