一、开始
很多的面试都会考查面试者对js异步模型的熟悉程度(本文仅代表Node11以后和浏览器的状况),会出一些简单的setTimeout和Promise结合的题目,例如:
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
如果出的题是下面这一道,你能说出他的输出顺序吗?
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函数变一下,你仍然能说出正确的顺序吗?
var run = () => Promise.resolve(2).then(val => console.log(val))
三、揭秘
async函数可以看成是基于Generator的一个语法糖,关于generator函数本文不作赘述,网上有很多写得很棒的文章,有兴趣可以了解一下,这里主要讲一下async函数包装后的代码运行顺序。
generator函数调用之后会给我们一个迭代器,提供给我门一个next(返回给我们value和done)的方法供我们惰性执行函数。对async函数来说,await后面的值会被封装成一个Promise,async函数帮我们做的就是执行这一个迭代器到结束。下面我们可以根据这个思路仿写一下:
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的异步顺序是执行完一个宏任务之后会清空微任务的队列,所以根据这个一个规则,你能说出第二段的中间两道题的输出顺序吗?
如果对本文有任何不妥之处,欢迎直接指出,交流学习😊