Javascript(异步)函数的输出顺序,你了解Async/await吗?

一、开始

很多的面试都会考查面试者对 js 异步模型的熟悉程度(本文仅代表 Node11 以后和浏览器的状况),会出一些简单的 setTimeout 和 Promise 结合的题目,例如:

1
2
3
4
5
6
console.log(1)
setTimeout(function() { console.log(2) })
new Promise(function(resolve) {
console.log(3)
resolve(4)
}).then(function(val) { console.log(val) })

然后问你顺序并问你为什么?对异步模型有了解的人应该都能说出来个大概(1,3,4,2)。

二、配合 async

如果出的题是下面这一道,你能说出他的输出顺序吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = async function() {
console.log(1)
await run()
console.log(3)
}
a()

setTimeout(function() {
console.log(4)
})

var run = () => { console.log(2) }

var b = new Promise((resolve) => {
console.log(5)
resolve(6)
console.log(7)
}).then((val) => console.log(val))

console.log(8)

如果你不了解 async 函数的运行机制,你可能一时语塞。

如果我把 run 函数变一下,你仍然能说出正确的顺序吗?

1
var run = () => Promise.resolve(2).then(val => console.log(val))

三、揭秘

async 函数可以看成是基于Generator的一个语法糖,关于 generator 函数本文不作赘述,网上有很多写得很棒的文章,有兴趣可以了解一下,这里主要讲一下 async 函数包装后的代码运行顺序。

generator 函数调用之后会给我们一个迭代器,提供给我门一个 next(返回给我们 value 和 done)的方法供我们惰性执行函数。对 async 函数来说,await 后面的值会被封装成一个 Promise,async 函数帮我们做的就是执行这一个迭代器到结束。下面我们可以根据这个思路仿写一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function co(genF) {
return new Promise(function(resolve, reject) {
const gen = genF(); // 先执行一次,拿到迭代器对象

function exec(nextF) {
let next;
try {
next = nextF(); // 执行next方法
} catch(e) {
return reject(e);
}
if(next.done) { // 如果done===true,则代表迭代结束
return resolve(next.value);
}
Promise.resolve(next.value).then(function(value) {
exec(function() { return gen.next(value); });
}, function(e) {
exec(function() { return gen.throw(e); });
});
}

exec(function() { return gen.next(undefined); }); // 调用next方法
});
}

js 的异步顺序是执行完一个宏任务之后会清空微任务的队列,所以根据这个一个规则,你能说出第二段的中间两道题的输出顺序吗?


如果对本文有任何不妥之处,欢迎直接指出,交流学习 😊