前言
让我们先来看看下面这道面试题👇
async function async1() {
console.log("async1 start");
await new Promise(resolve => {
console.log("promise1");
resolve("promise resolve");
});
console.log("async1 success");
return "async1 end";
}
console.log("script start");
async1().then(res => {
console.log(res);
});
new Promise(resolve => {
console.log("promise2");
setTimeout(() => {
console.log("timer");
});
});
其实并不是一道难题,依旧是经典的宏任务微任务来回切换,有一个需要注意的点就是async1
中的resolve
是不会生效的,因为async
函数只会把return
的内容当作Promise
对象返回
下面是答案👇
'script start'
'async1 start'
'promise1'
'promise2'
'async1 success'
'async1 end'
'timer'
看到这里或许你会觉得我在水经验-.-,但是其实我真正要说的是下面这一点
为什么promise2
会比async1 success
先输出呢?
要回答上面的问题,我认为有必要先对async/await
的原理有一个简单的了解
async/await是怎么实现的
在这里推荐由林三心
大佬写的7张图,20分钟就能搞定的async/await原理!为什么要拖那么久?,相信在看完之后你会对async/await
的原理有一个比较深入的理解,下面的内容也是对这篇文章的简单概括
回到主题,async/await
其实本质上是由generatoren 生成器
实现的,在生成器中可以使用以下的代码实现串行输出👇
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
由此大胆提出猜测
,async/await
将next()
函数放到了then()
方法中执行,也就是说第一个await函数
和之前的所有同步操作
其实是同步执行的,而第一个await之后的函数
其实就被放在了then()
中以链式回调的方式执行,这也就是为什么async/await
能在单个函数中实现异步函数同步执行
的原因
看到这里你可能还是有点懵,没关系,让我们来看看下面的async/await
的实现👇
function generatorToAsync(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments) // gen有可能传参
// 返回一个Promise
return new Promise((resolve, reject) => {
function go(key, arg) {
let res
try {
res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
} catch (error) {
return reject(error) // 报错的话会走catch,直接reject
}
// 解构获得value和done
const { value, done } = res
if (done) {
// 如果done为true,说明走完了,进行resolve(value)
return resolve(value)
} else {
// 如果done为false,说明没走完,还得继续走
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
}
}
go("next") // 第一次执行
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))
这里面有一句代码能够很好的解释上面的猜测
// 这里将`next()`函数的处理放到了`then()`中执行
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
也就是说,将next()
放到then
中执行的思路是没有问题的,那么我们就可以对面试题的问题进行解释了
为什么promise2
会比async1 success
先输出呢
有了上面的铺垫,要回答这个问题就不难了
async1 success
其实是被放到了then
中执行,而promise2
是同步代码。根据eventloop
的执行顺序,在执行完promise1
同步任务后就继续执行promise2
同步任务,等同步任务队列执行完后,再从微任务队列中取出async1 success
执行,这也就是为什么promise2
会比async1 success
先执行的原因
async function async1() {
console.log("async1 start");
await new Promise(resolve => {
console.log("promise1");
resolve("promise resolve");
});
// 这里的代码其实被放到了then()中执行,也就是放到了微任务队列中,先执行
console.log("async1 success");
return "async1 end";
}
console.log("script start");
async1().then(res => {
// 这里的微任务是在async函数的链式调用的,就会后执行
console.log(res);
});
new Promise(resolve => {
console.log("promise2");
setTimeout(() => {
console.log("timer");
});
});
结语
本文是我对这道面试题的一些个人见解,早上看到大佬的文章,下午刷题的时候就碰到了,就想着记录一下自己的理解
如果有任何错误和需要修改的地方,恳请指出,感激不尽~