如果了解 Promise 以及 async/await 的基本使用,就已经足以写出异步流程控制代码,但还有一些略微反直觉、容易记错的点,这些点基本都与 await 有关,本文是对此的一些整理和解析(示例代码大部分来自 MDN,对这些示例代码的解析并非照搬 MDN 原文)。
Promise 正常处理完成
await 关键字用于在 async function 的局部上下文中挂起(交出局部程序的执行权),直至右边的 Promise 返回。
最常规的情况是,await 操作符右边是一个 Promise,由于 await 将异步函数上下文中的执行权切换到了这个 Promise,这个 Promise 正常处理完成并返回其处理结果后,异步函数后续的语句才开始执行。
1 | function resolveAfter2Seconds(x) { |
Thenable objects
await 操作符右边还可以是 Thenable objects,也将同样会按照 resolve 或 reject 的执行来决定返回值。
1 | async function f2() { |
这个行为其实是为了与 Promise 的行为相匹配。
1 | async function f2() { |
转化为 Promise
await 右边的 value 如果不是 Promise,则处理为 Promise.resolve(value)。
1 | async function f3() { |
处理异常
如果 Promise 的执行中发生 reject,await 位置将抛出异常,reject 的值可以被 try catch 捕获。这里还可以看出,应该尽量抛出原生 Error,否则会丢失错误堆栈信息,而且 catch 到的值如果是各种类型都可能出现,处理起来也会更复杂。
1 | async function f4() { |
需要注意的是如果没有 await 操作符,即使在 return 语句中也无法被 try catch 捕获。不能捕获的原因,可以理解为没有使用 await 将异步操作“同步化”。
1 | async function f4() { |
这个例子还有一个变体,就是使用 Promise.reject(30).catch(e => e) 处理异常,可以用在不希望被 reject 干扰程序执行流程的情况。
1 | async function f4() { |
如果 Promise 中存在不受 Promise 流程控制的 setTimeout 之类的异步流程,并且发生异常,下面两种 catch 均不能捕获。
1 | async function f4() { |
更容易理解的一个例子是这样,意图是总能得到一个 response 值。
1 | var response = await promisedFunction().catch((err) => { console.error(err); }); |