面试题如下:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()
setTimeout(function() {
console.log('setTimeout')
})
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
.then(function() {
console.log('promise3')
})
Promise.resolve().then(function() {
console.log('promise4')
})
console.log('script end')
先打印输出:
Chrome环境:版本 89.0.4389.82(正式版本) (x86_64) 旧版本存在差异
eventloop.js:134 script start
eventloop.js:153 async2 end
eventloop.js:165 Promise
eventloop.js:181 script end
eventloop.js:155 async2 end1
eventloop.js:169 promise1
eventloop.js:178 promise4
eventloop.js:172 promise2
eventloop.js:143 async1 end
eventloop.js:175 promise3
eventloop.js:161 setTimeout
Node环境:v15.3.0
script start
async2 end
Promise
script end
async2 end1
promise1
promise4
promise2
async1 end
promise3
setTimeout
这道题涉及到的一些知识点:
a、EventLoop
b、Promise
tc39.es/ecma262/#se… tc39.es/ecma262/#se…
c、async/await
主要内容:
1、Promise的链式调用then函数是怎么执行的;
2、new Promise(r=>r())和Promise.resolve()分析;
3、async标识函数的特性;
4、await特性;
Promise的链式调用then函数是怎么执行的:
new Promise(resolve=>resolve())
.then(()=>{
console.log(1)
})
.then(()=>{
console.log(2)
})
.then(()=>{
console.log(3)
})
new Promise(resolve=>resolve())
.then(()=>{
console.log(4)
})
.then(()=>{
console.log(5)
})
.then(()=>{
console.log(6)
})
/**
输出:
1
4
2
5
3
6
*/
解析上面代码输出:
1、第一个Promise构造器的执行器函数(new Promise)内部的同步代码执行到 resolve(),此时状态变为_Promise {< fulfilled >: undefined}_,接下来将紧随的then的回调函数压入微任务队列中等待执行
此时的微任务队列 [ console.log(1) ]
2、接着继续执行,遇到第二个Promise构造器的执行器函数(new Promise)内部的同步代码执行到 resolve(),同第一个,此时将紧随的then中的回调函数添加到微任务队列中等待执行
此时的微任务队列 [ console.log(1) -> console.log(4) ]
3、全部同步任务代码执行完毕,开始执行微任务队列中任务,取队列第一个任务执行,打印 '1' ,将紧随then中回调函数添加到微任务队列中;( console.log(1) 出队列 )
取【console.log(1)】执行 输出 1 【console.log(1)】出队列【console.log(2)】入队列
[ console.log(4) -> console.log(2) ]
// 终端打印// 1
4、接下来就类似第3步;
取【console.log(4)】执行 输出 4 【console.log(4)】出队列【console.log(5)】入队列
[ console.log(2) -> console.log(5) ]
// 终端打印// 1 4
取【console.log(2)】执行 输出 2 【console.log(2)】出队列 【console.log(3)】入队列
[ console.log(5) -> console.log(3) ]
// 终端打印// 1 4 2
取【console.log(5)】执行 输出 5 【console.log(5)】出队列【console.log(6)】入队列
[ console.log(3) -> console.log(6) ]
// 终端打印// 1 4 2 5
取【console.log(3)】执行 输出 3 【console.log(3)】出队列
[ console.log(6) ]
// 终端打印// 1 4 2 5 3
取【console.log(6)】执行 输出 6 【console.log(6)】出队列
[ ]
// 终端打印// 1 4 2 5 3 6
总结:
- Promise.prototype.then 会隐式返回一个新的Promise;
- Promise的then回调是否会被推入微任务队列取决于Promise的状态是否是 fulfilled 或 rejected
- a.Promise的状态是pending时,调用then会在该Promise上注册一个回调,等待Promise状态变更;
- b.Promise的状态是fulfilled或rejected时,调用then会立即创建一个微任务,将注册的回调推入微任务队列中;
new Promise(r=>r())和Promise.resolve()分析
let v = new Promise(resolve => {
console.log("v-begin");
resolve("v-then");
});
// 1、new Promise(resolve => resolve(v))
new Promise(resolve => resolve(v))
// 2、Promise.resolve(v)
// Promise.resolve(v)
.then((v) => {
console.log(v)
});
new Promise(resolve => {
console.log(1);
resolve();
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
/**
// 1、使用new Promise(resolve => resolve(v))输出
v-begin
1
2
3
v-then
4
按照输出可以看出微任务队列如下:
[ console.log(2) -> console.log(3) -> console.log(v) -> console.log(4) ]
*/
/**
// 2、使用Promise.resolve(v)输出
v-begin
1
v-then
2
3
4
按照输出可以看出微任务队列如下:
[ console.log(v) -> console.log(2) -> console.log(3) -> console.log(4) ]
*/
可以看出new Promise(r=>r())和Promise.resolve() 在处理上有些不同,那到底各自是怎么去处理的呢?
1、先看Promise.resolve(v)
处理v参数简单说有以下几种情况
-
参数是一个 Promise 实例
如果参数是一个Promise实例,则不做任何处理,直接返回这个实例
-
参数是一个thenable对象
let thenable = { then: function(resolve, reject) { resolve(1); } };
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let v = { then: function(resolve, reject) { console.log("v-begin"); resolve("v-then"); } }; Promise.resolve(v) // 等价于 Promise.resolve().then(()=>{ console.log("v-begin"); return "v-then" })
-
参数不是具有then方法的对象,或者不是对象,再或者不传参数
直接返回一个resolved状态的 Promise 对象
注意:立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
那么对于Promise.resolve(v)处理的微任务队列入队出队如下:
let v = new Promise(resolve => {
resolve("v-then");
});
Promise.resolve(v)
.then((v) => {
console.log(v)
});
new Promise(resolve => {
resolve();
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
因为v是一个Promise实例,Promise.resolve(v)会原封不动的返回,则:
此时的微任务队列 [ console.log(v) ]
继续执行代码【console.log(2)】入队列
此时的微任务队列 [ console.log(v) -> console.log(2)]
取【console.log(v)】执行 输出 v-then 【console.log(v)】出队列
[ console.log(2) ]
// 终端打印// v-then
取【console.log(2)】执行 输出 2 【console.log(2)】出队列 【console.log(3)】入队列
[ console.log(3) ]
// 终端打印// v-then 2
取【console.log(3)】执行 输出 3 【console.log(3)】出队列 【console.log(4)】入队列
[ console.log(4) ]
// 终端打印// v-then 2 3
取【console.log(4)】执行 输出 4 【console.log(4)】出队列
[ ]
// 终端打印// v-then 2 3 4
2、分析new Promise(r=>r(v))
处理v参数分析有以下两种情况
-
参数不是具有then方法的对象,或者不是对象,再或者不传参数
直接返回一个resolved状态的 Promise 对象
-
参数是具有then方法的对象或直接是一个Promise对象
通过NewPromiseResolveThenableJob()将v(thenable)包装成Promise 对象,这个过程是一个微任务,会加入微任务队列,等到下次循环发现v(thenable)是一个resolved状态的Promise,然后会执行v(thenable)对象的then方法,回调加入微任务队列等待执行。(不管v是Promise还是非Promise的thenable对象都会进行一次包装)
let v = new Promise(resolve => {
resolve("v-then");
});
new Promise(resolve=>resolve(v))
.then((v) => {
console.log(v)
});
new Promise(resolve => resolve())
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
NewPromiseResolveThenableJob()对v进行包装,此过程是一个微任务,暂设置为【 p 】;
此时微任务队列[ p ]
执行到下面的new Promise(resolve => resolve()) 将【 console.log(2) 】入队列
[ p -> console.log(2) ]
取p执行,【 p 】出队列,发现v是resolved状态,则执行v的then方法,这也是一个微任务,暂设置为【 vt 】
[ console.log(2) -> vt]
取console.log(2)执行,【 console.log(2) 】出队列 【 console.log(3) 】 入队列
[ vt -> console.log(3) ]
// 终端打印// 2
取vt执行,【 vt 】出队列 new Promise(resolve=>resolve(v))执行完毕,返回一个Promise,并执行其then 将【 console.log(v) 】 入队列
[ console.log(3) -> console.log(v) ]
// 终端打印// 2
取console.log(3)执行,【 console.log(3) 】出队列 【 console.log(4) 】 入队列
[ console.log(v) -> console.log(4) ]
// 终端打印// 2 3
取console.log(v)执行,【 console.log(v) 】出队列
[ console.log(4) ]
// 终端打印// 2 3 v-then
取console.log(4)执行,【 console.log(4) 】出队列
[ ]
// 终端打印// 2 3 v-then 4
async标识函数的特性
async 函数是什么?一句话,它就是 Generator 函数的语法糖。 async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
- async修饰的函数必定返回一个 Promise 对象;
- async修饰的函数若没有返回值时,Promise的resolve方法会传递一个undefined值;
- async修饰的函数若有返回值时,Promise的resolve方法会传递这个值;
- async修饰的函数若抛出异常,Promise的reject方法会传递这个异常值;
async function add(){}
add().then(res=>console.log(res))
// 等价于
// Promise.resolve().then(res=>console.log(res))
// undefined
async function add(){
return 1
}
add().then(res=>console.log(res))
// 等价于
// Promise.resolve(1).then(res=>console.log(res))
// 1
async function add(){
return Promise.resolve().then(res=>console.log(111))
}
add().then(res=>console.log(res))
// 等价于
// Promise.resolve(Promise.resolve().then(res=>console.log(111)))
// .then(res=>console.log(res))
// -> Promise.resolve().then(res=>console.log(111))
// .then(res=>console.log(res))
// 111
// undefined
await特性
await v
- await后面的v会被转化成Promise;
- 即使 v 是一个已经fulfilled的Promise,还是会新建一个Promsie,并在这个新的Promise中resolve(v); 27.2.5.4.1-10
- await v 之后的代码等于传入then函数的回调 -> 6.2.3.1-11/12 完成新建Promise,恢复执行上下文
// 由上可对await v进行转换
async function async1() {
await async2()
console.log('async1 end')
}
等价于
async function async1() {
new Promise(resolve=>resolve(async2())).then(()=>{
console.log('async1 end')
})
}
最后我们对这道面试题进行分析
console.log('script start')
async function async1() {
// await async2()
// console.log('async1 end')
//1. 进行转换 ->
new Promise(resolve=>resolve(async2())).then(()=>{
console.log('async1 end')
})
}
// async function async2() {
// console.log('async2 end')
// return Promise.resolve().then(()=>{
// console.log('async2 end1')
// })
// }
//2. 进行转换 ->
function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()
setTimeout(function() {
console.log('setTimeout')
})
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
.then(function() {
console.log('promise3')
})
Promise.resolve().then(function() {
console.log('promise4')
})
console.log('script end')
[ ]:微任务队列 [[ ]]:宏任务队列
首先执行console.log('script start')
// 终端输出 script start
执行async1() -> new Promise(resolve=>resolve(async2()))
执行async2(),console.log('async2 end') 直接打印, then回调【 console.log('async2 end1') 】入队列
async2()执行完返回一个Promise,此时NewPromiseResolveThenableJob(async2())包装的Promise的then回调加入微任务队列,暂设置为【 p 】
[ console.log('async2 end1') -> p ]
// 终端输出 script start -> async2 end
继续向下执行,setTimeout 【【 console.log('setTimeout') 】】 入队列
[ console.log('async2 end1') -> p ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end
继续向下执行,console.log('Promise') 直接打印,then回调【 console.log('promise1') 】入队列
[ console.log('async2 end1') -> p -> console.log('promise1') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise
继续执行,then回调【 console.log('promise4') 】入队列
[ console.log('async2 end1') -> p -> console.log('promise1') -> console.log('promise4') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise
继续执行,console.log('script end') 直接打印
[ console.log('async2 end1') -> p -> console.log('promise1') -> console.log('promise4') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end
同步执行完毕,开始取微任务队列任务执行
取console.log('async2 end1'),【 console.log('async2 end1') 】出队列,此时async2()返回的Promise状态才转为resolved
[ p -> console.log('promise1') -> console.log('promise4') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1
取p执行,【 p 】出队列,发现async2()是resolved状态,则执行async2()的then,其回调是一个微任务,暂设置为【 vt 】, 【 vt 】入队列
[ console.log('promise1') -> console.log('promise4') -> vt ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1
取console.log('promise1'),【 console.log('promise1') 】出队列 【 console.log('promise2') 】 入队列
[ console.log('promise4') -> vt -> console.log('promise2') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1
取console.log('promise4'),【 console.log('promise4') 】出队列
[ vt -> console.log('promise2') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4
取vt执行,【 vt 】出队列 new Promise(resolve=>resolve(async2()))执行完毕,返回一个Promise,并执行其then 将【 console.log('async1 end') 】 入队列
[ console.log('promise2') -> console.log('async1 end') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4
取console.log('promise2'),【 console.log('promise2') 】出队列 【 console.log('promise3') 】 入队列
[ console.log('async1 end') -> console.log('promise3') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4 -> promise2
取console.log('async1 end'),【 console.log('async1 end') 】出队列
[ console.log('promise3') ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4 -> promise2 -> async1 end
取console.log('promise3'),【 console.log('promise3') 】出队列
[ ]
[[ console.log('setTimeout') ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4 -> promise2 -> async1 end -> promise3
取console.log('setTimeout'),【【 console.log('setTimeout') 】】出队列
[ ]
[[ ]]
// 终端输出 script start -> async2 end -> Promise -> script end -> async2 end1 -> promise1 -> promise4 -> promise2 -> async1 end -> promise3 -> setTimeout