JavaScript异步编程:async/await

842 阅读2分钟

同系列进阶文章:JavaScript异步编程:async底层原理与promise/generator的关系

语法简介

async/await是一种方便使用promise的特殊语法。

函数前面的async表示:此函数将会返回一个promise,如果函数返回的不是promise,将会包装成一个已经resolved的promise。

async function f() {
  return 1;
}
f().then(alert); // 1

// 相当于下面的写法
async function f() {
  return Promise.resolve(1);
}
f().then(alert); // 1

await只在async函数中有效,普通函数中使用await会报语法错误。

let value = await promise; 表示让JavaScript引擎等待直到 promise 完成并返回结果。这个行为不会耗费 CPU 资源,因为引擎可以同时处理其他任务:执行其他脚本,处理事件等。

async function f() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });
  let result = await promise; // 等待直到 promise resolved后拿到result继续往下执行
  alert(result); // "done!"
}
f();

await可以接收Thenable对象(具有 then 方法的对象)并调用then方法,并将resolve,reject 作为参数传入。然后await等到这两个方法中的某个被调用,再处理得到的结果。

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    setTimeout(() => resolve(this.num * 2), 1000);
  }
};
async function f() {
  // 等待 1 秒, result 变为 2
  let result = await new Thenable(1);
  alert(result);
}
f();

如果想定义一个 async 的类方法,在方法前面添加 async 就可以了:

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}
new Waiter().wait().then(alert); // 1

错误处理

如果一个promise被resolved,await promise返回的就是其结果。 如果一个promise被rejected,就会抛出一个错误,就像在那一行有个 throw 语句那样。

async function f() {
  await Promise.reject(new Error("Whoops!"));
}
// 相当于:
async function f() {
  throw new Error("Whoops!");
}

可以用 try..catch 来捕获上面的错误,就像对一般的 throw 语句那样:

async function f() {
  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}
f();

如果不使用 try..catch,由f() 产生的 promise 就会被reject,可以在函数调用后添加 .catch 来处理错误。如果没处理错误可以使用全局的unhandledrejection事件来捕获。

async function f() {
  let response = await fetch('http://no-such-url');
}
// f() 变为一个被reject的 promise
f().catch(alert); // TypeError: failed to fetch // (*)

当需要等待多个promise时,可以用Promise.all搭配async/await使用:

async () => {
  let results = await Promise.all([
    fetch(url1),
    fetch(url2),
    ...
  ]);
}

如果发生错误,也会正常传递:先从失败的 promise 传到 Promise.all,然后变成我们能用 try..catch 处理的异常。

执行顺序

Async/await 是基于 promise 的,所以它内部使用相同的微任务队列,并且相对宏任务来说具有更高的优先级。

async function f() {
  return 1;
}
(async () => {
    setTimeout(() => alert('timeout'), 0); // 3

    await f(); // 1

    alert('await'); // 2
})();

在一个普通的函数中,如何调用另一个 async 函数并且拿到返回值?使用then,因为async函数返回一个settled promise。

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return 10;
}
(function() {
  wait().then((value) => alert(value));
})();