学习链接

阮一峰:async

异步迭代和 generatorSymbol.asyncIteratorfor await ... of

异步操作-async

异步的角度来看,async 算是 generator 的语法糖。

async 函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器

  • Generator 函数的执行需要调用 next 方法,或者用 co 模块,也就是需要依靠执行器,才能真正执行,得到最后结果。

  • async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。

(2)更好的语义

  • asyncawait,比起星号和 yield,语义更清楚了。

  • async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性

  • co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象。

  • async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值
    (数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)

(4)返回值是 Promise

  • async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。可以用 then 方法指定下一步的操作。

  • 进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。

语法

异步操作-async - 图1

  1. // 继发
  2. let foo = await getFoo();
  3. let bar = await getBar();
  4. // 执行完 getFoo() 才会执行 getBar()
  5. // 同时触发写法一
  6. let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  7. // 同时触发写法二
  8. let fooPromise = getFoo();
  9. let barPromise = getBar();
  10. let foo = await fooPromise;
  11. let bar = await barPromise;

并发执行、继发执行

  1. function dbFuc(db) { //这里不需要 async
  2. let docs = [{}, {}, {}];
  3. // 可能得到错误结果
  4. docs.forEach(async function (doc) {
  5. await db.post(doc);
  6. });
  7. }
  8. // 上面代码可能不会正常工作
  9. // 原因是这时三个db.post()操作将是并发执行,也就是同时执行,而不是继发执行
  10. // 改为 for 循环
  11. async function dbFuc(db) {
  12. let docs = [{}, {}, {}];
  13. for (let doc of docs) {
  14. await db.post(doc);
  15. }
  16. }
  17. // 用数组的 reduce() 方法
  18. async function dbFuc(db) {
  19. let docs = [{}, {}, {}];
  20. await docs.reduce(async (_, doc) => {
  21. await _;
  22. // 等待 上一次 值为 undefined 状态为 fulfilled 的对象
  23. await db.post(doc); // 异步操作
  24. // return undefined
  25. }, undefined);
  26. }

可保留运行堆栈

  1. const a = () => {
  2. b().then(() => c());
  3. };

上面代码中,函数 a 内部运行了一个异步任务 b()

b() 运行的时候,函数 a() 不会中断,而是继续执行。

等到 b() 运行结束,可能 a() 早就运行结束了,

b() 所在的上下文环境已经消失了。

如果 b()c() 报错,错误堆栈将不包括 a()

  1. const a = async () => {
  2. await b();
  3. c();
  4. };

上面代码中,b() 运行的时候,a() 是暂停执行,上下文环境都保存着。一旦 b()c() 报错,错误堆栈将包括 a()

async 实现原理

Generator 函数和自动执行器包装在同一个函数中。

  1. async function fn(args) {
  2. // ...
  3. }
  4. // 等同于
  5. function fn(args) {
  6. return spawn(function* () {
  7. // ...
  8. });
  9. }
  1. function spawn(genF) {
  2. return new Promise((resolve, reject) => {
  3. const gen = genF();
  4. step(() => gen.next(undefined));
  5. function step(nextF) {
  6. let next;
  7. try {
  8. next = nextF();
  9. } catch (e) {
  10. return reject(e);
  11. }
  12. if (next.done) {
  13. return resolve(next.value);
  14. }
  15. Promise.resolve(next.value).then(
  16. v => { step(() => gen.next(v)); },
  17. e => { step(() => gen.throw(e)); }
  18. );
  19. }
  20. });
  21. }