一、开始
很多的面试都会考查面试者对 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 的异步顺序是执行完一个宏任务之后会清空微任务的队列,所以根据这个一个规则,你能说出第二段的中间两道题的输出顺序吗?
如果对本文有任何不妥之处,欢迎直接指出,交流学习 😊