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

1,318 阅读2分钟

一、开始

很多的面试都会考查面试者对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的异步顺序是执行完一个宏任务之后会清空微任务的队列,所以根据这个一个规则,你能说出第二段的中间两道题的输出顺序吗?


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