在牛客上看的面试题,引发的……
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 start');
return new Promise((resolve, reject) => {
resolve();
console.log('async2 promise');
})
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
console.log('script end')
根据这道题整理了以下知识点
js执行机制
JavaScript是一门单线程语言,是按照语句的顺序自上而下进行执行的。将任务可以分为两类
- 同步任务:需要执行的任务在主线程上排队,直接执行。
- 异步任务:没有立即执行的任务但是需要被执行的任务,放在任务队列里面
JavaScript事件循环
- 所有同步任务在主线程上执行,形成执行栈
- 主线程之外还有一个任务队列(task queue),负责执行异步任务,任务队列分为宏任务(macro-task)和微任务(micro-task)
- 一旦执行栈中同步任务执行完毕,就会开始执行任务队列
宏任务和微任务
macro-task(宏任务):
- 包括整体代码script
- setTimeout
- setInterval
- setImmediate (Node独有)
- requestAnimationFrame (浏览器独有)
- I/O
- UI rendering (浏览器独有)
micro-task(微任务):
- Promise
- process.nextTick(Node独有)
- Object.observe
- MutationObserver
不同的任务会进入不到不同的event queue
而任务队列的执行如上图所示,当同步任务执行完,开始执行任务队列
- 是否有可执行的微任务
- 如果有,执行所有的微任务
- 如果没有,开始执行新的宏任务
- 当宏任务执行完,再回到 ‘是否有可执行的微任务’
所以任务队列会开优先执行微任务,只有当所有的微任务执行完毕才会执行一条宏任务
代码演示:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve(true)
}).then(function() {
console.log('then');
})
console.log('console');
// promise
// console
// then
// setTimeout
Promise的resolve和reject
promise中的代码是同步的,异步体现在resolve和reject
而promise中的resolve会放到任务队列的微任务中
注意,调用resolve
或reject
并不会终结 Promise 的参数函数的执行。
代码演示:
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面代码中,调用resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
async await
async函数中的代码,在出现await之前也是同步执行的。
async await本身是promise + generator的语法糖,而await 后面必须跟Promise对象
所以await后面的代码是放到微任务中的,这个时候就会跳出async函数,继续执行后面的代码。
面试题中的
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
相当于
async function async1() {
console.log('async1 start')
// Promise.resolve(async2()).then(() => {
// console.log('async1 end')
//})
new Promise((reslove) => {
reslove(async2())
}).then(() => {
console.log('async1 end')
})
}
回到面试题
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 start');
return new Promise((resolve, reject) => {
resolve();
console.log('async2 promise');
})
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
console.log('script end')
1.开始执行同步语句:
step1
console.log('script start');
执行栈:[console.log(‘script start’)]
宏任务: []
微任务: []
打印结果: script start
step2
setTimeout(function() { console.log('setTimeout'); }, 0);
执行栈:[]
宏任务: [setTimeout]
微任务: []
step3
async1();
开始执行async1()
await async2()
可以看作是一个new Promise()对象
执行栈:[console.log(‘async1 start’)]
宏任务: [setTimeout]
微任务: [Promise1.resolve(async2()), Promise1.resolve(async2()).then(()=>console.log(‘async1 end’) )]
打印结果:
script start
async1 start
step4
执行async2()
执行栈:[console.log(‘async2 start’), console.log(‘async2 promise’)]
宏任务: [setTimeout]
微任务: [Promise.resolve(), Promise.resolve().then(() => console.log(‘async1 end’) )]
打印结果:
script start
async1 start
async2 start
async2 promise
step5
顺序执行new Promise()
执行栈:[console.log(‘promise1’),console.log(‘script end’)]
宏任务: [setTimeout]
微任务:[Promise.resolve(), Promise.resolve().then(() => console.log(‘async1 end’) ), Promise.reslove(‘promise2’) , Promise.reslove(‘promise3’)]
打印结果: script start async1 start async2 start async2 promise promise1 script end
setp6
再开始执行宏任务前,看微任务队列是否为空。开始执行微任务。
Promise.resolve(),Promise.reslove(‘promise2’) , Promise.reslove(‘promise3’)是同级的。
打印结果: script start async1 start async2 start async2 promise promise1 script end promise2 promise3
由于微任务队列还不为空,继续执行微任务Promise.resolve().then(() => console.log(‘async1 end’)
打印结果: script start async1 start async2 start async2 promise promise1 script end promise2 promise3 async1 end
step7
微任务队列为空,开始执行宏任务
打印结果: script start async1 start async2 start async2 promise promise1 script end promise2 promise3 async1 end setTimeout
总结
执行任何一个宏任务之前都会查看微任务队列,只有微任务队列为空才会执行宏任务。
async await 本身是promise +generator的语法糖,可以转化为new Promise进行分析。
参考:
js执行机制(promise,setTimeout执行顺序)
阮一峰的ES6入门