JavaScript筑基(八):async & await

158 阅读3分钟

async & await

自动化生成器

上一节中提到了 async / awaitPromise + generator 的语法糖,那么我们先来看看,这两个东西组合起来能做什么事情

// 模拟网络请求
function requestData(data) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(data.toUpperCase());
    }, 1000);
  });
}

// 生成器
function* getData() {
  const res1 = yield requestData("name: ");
  const res2 = yield requestData(res1 + "aa");
  const res3 = yield requestData(res2 + "bb");
  console.log("result: " + res3);
}

// 自动执行的生成器
function createAutoGen(genFn) {
  const generator = genFn();
  function handler(res) {
    const result = generator.next(res);
    if (result.done) {  // 如果没有迭代结束,那就返回
      return result.value;
    }
    // 多套一层 Promise.resolve 是为了处理 yield 返回非 Promise 对象的情况,可以统一处理
    Promise.resolve(result.value).then((res) => {
      console.log(res);
      handler(res);     // 自动执行下一次 next
    });
  }
  handler();
}

createAutoGen(getData);
/*  console.log
NAME: 
NAME: AA
NAME: AABB
result: NAME: AABB
*/

上面的代码实现了当上一个异步请求结束后,才会自动执行下一段代码的功能

用过 async / await 的同学应该已经看出来了,这个好像就是实现了异步函数。确实是这样,为了后文更好的理解关键字 await返回值 问题,我们可以先了解 async 异步函数的处理场景。

比起生成器,async / await 是使用更加高频的一组关键字: async 用于声明异步函数,await 用于控制暂停与执行的流程

await

在了解了生成器和 Promise 勾结在一起之后会发生什么奇妙的事情之后,那么理解 await 也就水到渠成了。await 可以理解为升级版的 yield

关注以下几点

  • yield 不太一样,await 会自动控制下一段代码的执行,不需要自己在外部调用形如 next 的函数(或者封装控制生成器自动执行的函数)
  • 因为不需要外部调用控制暂停与执行,所以 await 获取的值与右侧执行语句的返回值有关
  • await 可以理解为 Promise.resolve() 的语法糖,上文就是其简易实现。 因此 await 获取的值也就是右侧执行语句 resolve(value) 中的 valueawait 后续的代码可以等效为装在 Promise.resolve(右侧表达式).then(value => { ...装在这里 }) 的代码

我们在这里验证一下 await 后面的代码是否是微任务,即上文的实现功能

async function foo() {
  console.log("async ----- start");

  const res1 = await 1;
  console.log(res1);
  const res2 = await 2;
  console.log(res2);

  console.log("async ----- end");
  return "end";
}

console.log("script start");

foo();

Promise.resolve("p1").then((res) => console.log(res));

setTimeout(() => {
  console.log("setTimeout");
}, 100);

console.log("script end");

/*  console.log
script start
async ----- start
script end
1
p1
2
async ----- end
setTimeout
*/

经过验证可以看出来 foo 中第一个 await 之前的代码会同步执行,而后续的代码则是作为微任务执行的

返回值

使用 async function 声明的异步函数返回值是一个 Promise ,如果函数中存在 await 关键字,那么返回的对象就是 <pending> 状态,需要使用另一个 await 接收或者使用 then 处理

使用下面的代码来验证一下

async function foo() {
  await "wait";
  return "end";
}

// 不做任何处理
async function oriHandler() {
  const res = foo();
  console.log(res);     // Promise { <pending> }
}

// 使用 await
async function awaitHandler() {
  const res = await foo();
  console.log(res);     // end
}

// 使用 then
async function thenHandler() {
  const res = foo();
  res.then((value) => {
    console.log(value); // end
  });
}

oriHandler();
awaitHandler();
thenHandler();

小结

如果已经理解了生成器的作用以及其与 Promise 组合使用的实现,在这个基础之上来理解 async / await ,多少会有点收获,同时以后再来看异步函数,可以更加深入的角度来思考问题。

参考资料

《JavaScript高级程序设计》(第四版)

《JavaScript忍者秘籍》(第二版)

后续

...施工中