await

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

Generator 函数,依次读取两个文件。

const fs = require("fs");
 
const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function (error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};
 
const gen = function* () {
  const f1 = yield readFile("/etc/fstab");
  const f2 = yield readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};

抛出错误

等待会抛出错误的同步操作,会返回拒绝的期约:

async function foo() {
 console.log(1);
 await (() => { throw 3; })();
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3

单独的 Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。不 过,对拒绝的期约使用 await 则会释放(unwrap)错误值(将拒绝 Promise 返回):

async function foo() {
 console.log(1);
 await Promise.reject(3);
 console.log(4); // 这行代码不会执行
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3

异步策略

实现 sleep

async function sleep(delay){
    return new Promise((resolve)=> setTimeout(resolve, delay))
}

利用平行执行

正常串行写法,在不需要保证顺序的情况下属于需要优化的地方

async function randomDelay (id) {
  // 延迟 0~1000 毫秒
  const delay = Math.random() * 1000;
  return new Promise((resolve) => setTimeout(() => {
    console.log(`${id} finished`);
    resolve();
  }, delay));
}
async function foo () {
  const t0 = Date.now();
  await randomDelay(0);
  await randomDelay(1);
  await randomDelay(2);
  await randomDelay(3);
  await randomDelay(4);
  console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
 
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 1570ms elapsed

for 循环写法

async function foo () {
  const t0 = Date.now();
  for (let i = 0; i < 5; ++i) {
    await randomDelay(i);
  }
  console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
 
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 2494ms elapsed

就算这些期约之间没有依赖,异步函数也会依次暂停,等待每个超时完成。这样可以保证执行顺序, 但总执行时间会变长。

如果顺序不是必需保证的,那么可以先一次性初始化所有期约,然后再分别等待它们的结果。

async function foo () {
  const t0 = Date.now();
  const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
  for (const p of promises) {
    await p;
  }
  console.log(`${Date.now() - t0}ms elapsed`);
}
foo();

虽然 Promise 没有按照顺序执行,但 await 按顺序收到了每个 Promise 的值

 function foo () {
  const t0 = Date.now();
  const promises = Array(5).fill(null).map((_, i) => randomDelay(i));
  for (const p of promises) {
    console.log(`awaited ${await p}`);
  }
  console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
 
// 1 finished
// 3 finished
// 0 finished
// awaited 0
// awaited 1
// 2 finished
// awaited 2
// awaited 3
// 4 finished
// awaited 4
// 958ms elapsed

async 函数

const asyncReadFile = async function () {
  const f1 = await readFile("/etc/fstab");
  const f2 = await readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};

async 函数对 Generator 函数的改进,体现在以下四点
(1)内置执行器。
(2)更好的语义
(3)更广的适用性。
(4)返回值是Promise

async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。

实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {
  // ...
}
 
// 等同于
 
function fn(args) {
  return spawn(function* () {
    // ...
  });
}

spawn

function spawn(genF) {
  return new Promise(function (resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch (e) {
        return reject(e);
      }
      if (next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(
        function (v) {
          step(function () {
            return gen.next(v);
          });
        },
        function (e) {
          step(function () {
            return gen.throw(e);
          });
        }
      );
    }
    step(function () {
      return gen.next(undefined);
    });
  });
}

Async 和 await 的优化

性能提升取决于以下三个因素:

  • TurboFan,新的优化编译器 🎉
  • Orinoco,新的垃圾回收器 🚛
  • 一个 Node.js 8 的 bug 导致 await 跳过了一些微 tick(microticks) 🐛

bug 优化

const p = Promise.resolve();
 
(async () => {
  await p;
  console.log("after:await");
})();
 
p.then(() => console.log("tick:a")).then(() => console.log("tick:b"));

通过判断 await 后的 是否是一个 Promise,如果是的话直接返回,可以节省一个 Promise,只封装必要的 promise。这个操作在值已经是 promose 的情况下可以省去一个额外的 promise 和两个微任务。

去除了 throwaway

「译」更快的 async 函数和 promises