从一个题目出发今天看到一道面试题,是关于async/await、promise和setTimeout的执行顺序的。题目如下:asyncfunctionasync1(){console.log('async1start');awaitasync2();console.log('asnyc1end');}asyncfunctionasync2(){console.log('async2');}console.log('脚本启动');setTimeout(()=>{console.log('setTimeOut');},0);async1();newPromise(function(reslove){console.log('promise1');reslove();}).then(function(){console.log('promise2');})console.log('脚本结束');复制代码我给的答案:scriptstartasync1startasync2asnyc1end//xpromise1scriptendpromise2setTimeOut复制代码正确答案:scriptstartasync1startasync2promise1scriptendasnyc1endpromise2setTimeOut复制代码为什么promise1在asnyc1结束之前就出来了?带着这个疑问,我去了解事件循环机制。jsEventLoop事件循环机制JavaScript事件分为两种:宏任务(macro-task)微任务(micro-task)脚本承诺。(Node.js环境)Object.observeIO操作xUI交互事件xpostMessagexMessageChannelx事件的执行顺序是先执行宏任务,再执行微任务。这是基础。任务可以有同步任务和异步任务。进入事件表并注册函数。异步事件完成后,回调函数会被放入EventQueue(宏任务和微任务是不同的EventQueue)。同步任务执行完成后,会从EventQueue中读取事件,放到主线程执行时,回调函数也可能包含不同的任务,所以会循环执行以上操作。注意:setTimeOut并不是直接将你的回调函数放入上述异步队列中,而是在定时器超时后将回调函数放入执行异步队列中。如果此时这个队列中已经有很多任务,就会排在它们后面。这也解释了为什么setTimeOut不能准确执行。setTimeOut的执行需要满足两个条件:主进程必须空闲。如果时间到了,主进程不会空闲,不会执行你的回调函数。这个回调函数在插入异步队列的时候需要等到前面的异步函数执行完。Promise,async/await会先执行,newPromise是一个同步任务,会放到主进程中立即执行。.then()函数是一个异步任务,会放入异步队列,那么什么时候放入异步队列呢?当你的promise状态结束时,它会立即被放入异步队列。带有async关键字的函数将返回一个promise对象。如果里面没有await,执行相当于一个普通的函数;如果没有await,async的功能是不是很强大?await关键字必须在async关键字函数内部,await写在外面会报错;await就像它的语义一样,是等待,等待右边的表达式完成。这个时候await会放出线程,把后面的代码阻塞在async里面,先执行async外面的代码。外层的同步代码执行完后,再执行里面的后续代码。即使await不是一个promise对象,而是一个同步函数,它也会等待这个操作。流程梳理我们整体梳理一下上述代码执行的流程:整个代码片段(脚本)将console.log('scriptstart')作为宏任务执行,并输出scriptstart;执行setTimeout,这是一个异步动作,将其放入宏任务的异步队列中;执行async1(),输出async1start,继续向下执行;执行async2(),输出async2,并返回一个promise对象,await放弃线程,将返回的promise加入到microtask异步队列中,因此async1()下面的代码应该等待上面的完成,继续执行;执行newPromise,输出promise1,然后将resolve()放入microtask异步队列;执行console.log('scriptend'),输出scriptend;至此所有的同步代码都执行完毕,然后去microtask异步队列中获取任务,然后执行resolve(由async2返回的promise返回),输出async1end;然后执行resolve(ofnewPromise),输出promise2;最后执行setTimeout,输出settimeout。第4步,这里await有一个机制,就是await的等待,不会阻塞外部函数的执行,如果await是在等待一个Promise,Promise中的代码还是会同步执行。如果不是Promise,会使用Promise.resolve封装,其中async2是async方法,里面的打印会同步执行,awaitasync2()之后的代码会放在microtask队列的第一个位置,等待外部同步代码执行完毕。所以我知道为什么脚本结束优先于async1结束输出。
