浅析async await错误处理问题及如何不使用try catch优雅地进行错误处理

一、Async 函数的错误处理

  async 函数的语法不难,难在错误处理上。先来看下面的例子:

  我们可以看到 Promise 报错后,a = await 1 并没有被执行。即当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。

  解决办法是:可以添加 try catch。

// 正确的写法
let a;
async function correct() {
    try {
        await Promise.reject('error')
    } catch (error) {
        console.log(error);
    }
    a = await 1;
    return a;
}
correct().then(v => console.log(a));

  这样就会先打印 error,再打印 1。因此,如果有多个 await 则可以将其都放在 try/catch 中,但很显然,这样并不优雅。

// 我们需要对每一次异步操作进行错误处理
function async asyncTask(cb) {
    try {
      const asyncFuncARes = await asyncFuncA()
    } catch(error) {
      return new Error(error)
    }
    try {
      const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
    } catch(error) {
      return new Error(error)
    }
    try {
      const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
    } catch(error) {
      return new Error(error)
    }
}

二、npm包:await-to-js 及源码分析

  作者是这样介绍这个库的:Async await wrapper for easy error handling without try-catch。中文翻译过来就是:无需 try-catch 即可轻松处理错误的异步等待包装器。

  与上面对比,使用了await-to-js之后,我们可以这样的处理错误:

import to from './to.js';
function async asyncTask() {
   const [err, asyncFuncARes]  = await to(asyncFuncA())
   if(err) throw new (error);
   const [err, asyncFuncBRes]  = await to(asyncFuncB(asyncFuncARes))
   if(err) throw new (error);
   const [err, asyncFuncCRes]  = await to(asyncFuncC(asyncFuncBRes)
   if(err) throw new (error);
}

  简洁多了,看看源码,没写想到只有 15 行,完全都可以自己作为一个公共方法使用即可。

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;

  上面是TS版的源码,但是考虑到有些同学可能还没接触过TS,我着重分析一下下面这版JS版的源码。

export function to(promise, errorExt) {
    return promise
        .then((data) => [null, data])
        .catch((err) => {
          if (errorExt) {
             const parsedError = Object.assign({}, err, errorExt);
             return [parsedError, undefined];
          }
          return [err, undefined];
      });
}
export default to;

  这里我们先抛开 errorExt 这个自定义的错误文本,可以看出:其代码的逻辑用中文解释是这样的:

  • 无论成功还是失败,都返回一个数组,数组的第一项是和错误相关的,数组的第二项是和响结果相关的
  • 成功的话,数组第一项也就是错误信息为空,数组第二项也就是响应结果正常返回
  • 失败的话,数组第一项也就是错误信息为错误信息,数组第二项也就是响应结果返回undefined

  经过上面的分析发现:其实挺简单的,但是,又确实挺巧妙的,我们也并不是做不到,而不是想不到。

  这里我们再来看函数 to 的第二个参数 errorExt,不难发现这其实就是拿来用户自定义错误信息的,通过 Object.assign 将正常返回的 error 和用户自定义的,合并到一个对象里面供用户自己选择。

posted @ 2021-05-17 22:08  古兰精  阅读(2162)  评论(0编辑  收藏  举报