【若川视野 x 源码共读】第21期 | await-to-js 如何在async函数中优雅地捕获 Promise 错误

144 阅读2分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

思考一个问题:怎样捕获async函数中Promise的错误?

在async函数中,常见捕获Promise错误方式有两种:

  1. Promise.prototype.catch()
async function demo() {
    Promise.reject('failed')
    .catch(console.log); // failed
}
demo();

本质上是Promise的链式调用。仍然是传统异步代码的书写方式。

  1. try catch,一般用于async函数的内部
async function demo() {
    try {
       await Promise.reject('failed');
    } catch (e) {
        console.log(e); // failed
    }
}
demo();

try catch的书写方式更近似同步代码,但是每个报错的地方都要try catch, 不够优雅。

有没有办法既实现近似同步代码的书写体验又减少重复的try catch,优雅地捕获错误呢?await-to-js提供了一种思路。

用法举例

import to from 'await-to-js';

const successTask = () => {
    return Promise.resolve('success');
};
const failedTask = () => {
    return Promise.reject(new Error('failed'));
};

async function demo() {
    // to方法第一个参数接收Promise,await后的结果是一个数组。
    const [err1, data1] = await to(successTask());
    // 返回值数组的第一个元素保存Promise抛出的异常
    console.log(err1); // null 
    // 返回值数组的第二个元素保存Promise的返回值
    console.log(data1); // success
    const [err2, data2] = await to(failedTask());
    console.log(err2!.message); // failed
    console.log(data2); // undefined
    // to第二个参数是用户自定义的错误信息,可以补充或覆盖原来的错误信息
    const [err3] = await to(failedTask(), { message: 'Mock Error Info' });
    console.log(err3!.message); // Mock Error Info
}

demo();

源码解析

await-to-js 源码非常简单,只有一个函数to。

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
export function to<T, U = Error> (
  promise: Promise<T>,
  errorExt?: object
): Promise<[U, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((err: U) => {
      if (errorExt) {
        const parsedError = Object.assign({}, err, errorExt);
        return [parsedError, undefined];
      }

      return [err, undefined];
    });
}

export default to;

1)to 支持传两个参数,第一个参数接收待执行的Promise,第二个参数接收用户自定义的错误信息。

2)通过await得到一个长度为2的数组。数组的第一个元素为Promise抛出的错误,第二个元素为Promise的返回值。

3)to 内部通过Promise.prototype.catch方法捕获错误。

基于to的封装和抽象,我们可以用一行代码得到Promise执行结果并优雅地捕获Promise的错误。

async function demo() {
    const [err, data] = await to(anyPromiseTask());
}
demo();

总结

await-to-js 基于Promise.prototype.catch优雅地封装了Promise可能抛出的异常,使我们可以用更加近似同步代码的书写方式书写Promise异步代码。