首先咱们来看一道题目,以下javascript代码,执行后会在控制台打印出什么内容?javascript
1 async function async1() { 2 console.log('async1 start'); 3 await async2(); 4 console.log('async1 end'); 5 } 6 7 async function async2() { 8 console.log('async2 start'); 9 return new Promise((resolve, reject) => { 10 resolve(); 11 console.log('async2 promise'); 12 }) 13 } 14 15 console.log('script start'); 16 setTimeout(function() { 17 console.log('setTimeout'); 18 }, 0); 19 20 async1(); 21 22 new Promise(function(resolve) { 23 console.log('promise1'); 24 resolve(); 25 }).then(function() { 26 console.log('promise2'); 27 }).then(function() { 28 console.log('promise3'); 29 }); 30 console.log('script end');
说实话,真正能在面试中把这道题目答对的前端工程师百里挑一。咱们先来瞧一下答案吧。把以上代码存到test.js文件中,并用node执行一下,结果以下:html
若是把以上代码贴到一个网页中的script标签里面,而后打开这个网页,再打开控制台,能够看到以下输出(Chrome 64位 63.0.3239.84):前端
结果和node打印的如出一辙。那么为何是这个顺序呢?html5
咱们都知道js的单线程特性(html5的web worker不算在内~)以及良好的异步支持。在单线程的前提下,异步任务到底何时开始执行,实际上是有两个队列来进行管理,即Macrotask和Microtask(只有一个字母的差距,不要认错……)。在当前正在执行的线程中,若是碰到属于Macrotask的异步任务,则放入Macrotask队列;碰到Microtask的异步任务则放入Microtask队列。注意这里只是把任务放入队列,并不会执行它。等到当前主线程任务执行完毕以后,会依次从Microtask队列中取出任务执行,在执行期间固然仍是遵循碰到异步任务放入相应队列的原则。等到Microtask任务所有执行过了,此时再从Macrotask队列中取出一个任务执行。java
属于Macrotask的任务有:node
setTimeout,setInteveral,script标签,I/O,UI渲染
属于Microtask的任务有:web
Promise,async/await,process.nextTick,Object.observe,MutationObserver
(事实上,即便一样是Microtask,内部也是有优先级的差异的,例如NodeJS的实现上,process.nextTick比Promise要先执行。相关问题能够瞧瞧这个链接:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 。反正我瞧到一半就放弃了,好在async/await和Promise没有优先级差异)面试
而后咱们来分析一下本题中的执行顺序:promise
【1】第15行执行,打印出script start前端工程师
【2】第16至18行,把回调任务放入Macrotask (目前Macrotask:第16行setTimeout,Microtask:空)
【3】第20行,执行async1函数,先打印出第2行的async1 start
【4】第3行的async2先执行,打印出第8行的async2 start
【5】第9行至第12行遇到Promise,先打印出第11行的async2 promise(注意无论你resolve写在new Promise的函数什么位置,都跟写到最后一句同样!)
【6】第3行的async2返回了Promise,而且async2前面有await修饰,所以后面第4行的任务被放到Microtask(目前Macrotask:第16行setTimeout,Microtask:第4行)
【7】第22至25行,打印出promise1,并把第26行放入Microtask,注意第28行还没执行到,因此这行什么都不作(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)
【8】第30行打印script end(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)
【9】脚本主线程执行结束,如今拿出来一个Microtask,即第4行,打印async1 end(目前Macrotask:第16行setTimeout,Microtask:第26行)
【10】再拿出来一个Microtask,即第26行,打印promise2,此时因为第26行后面跟着then,因此把第28行插入Microtask(目前Macrotask:第16行setTimeout,Microtask:第28行)
【11】再拿出来一个Microtask,即第28行,打印promise3(目前Macrotask:第16行的setTimeout,Microtask:空)
【12】Microtask没有了,执行下一个Macrotask,即第16行的setTimeout,打印setTimeout,结束
须要注意的是,如下两种写法,效果是如出一辙的(resolve的位置无所谓):
写法1: new Promise((resolve, reject) => { console.log('1111'); resolve(); console.log('2222'); }); 写法2: new Promise((resolve, reject) => { console.log('1111'); console.log('2222'); resolve(); });
另外,对于Promise的链式调用,如new Promise(....).then(...).then(...)....,一次只放第一个then的内容进入Microtask,等第一个then执行的时候,会把第二个then放入Microtask,而不是一次把两个then都放进去。