停止这样使用‘async/await’

最近看到一些开发人员使用这种方法来处理 async/await 错误:

/**
 * @param { Promise } promise
 * @param { Object= } errorExt - 可以传递给 err 对象的附加信息
 * @return { Promise }
 */
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];
    });
}

async function doSomething() {
  const [error1, result1] = await to(fetch(''));
  if (error1) {
    return;
  }

  const [error2, result2] = await to(fetch(result1));
  if (error2) {
    return;
  }
  // ...
}

正如你所看到的,他们封装了将原始 Promise 转换为肯定会成功并返回数组的“Promise”的函数。

如果原始 Promise 成功,则数组中的第一项为 null 表示没有错误,第二项是原始 Promise 的结果。 如果原始 Promise 失败,则数组的第一项是错误的,第二项是未定义的。 仅此而已。

他们认为它很优雅,并使代码更具可读性。 但我不这么认为,也不建议大家这样用!

我认为这样的封装有点过度设计,在大多数情况下,我们其实不需要。 接下来,我将从两个角度阐述该观点:

1.从设计角度出发

async/await API 的目的是让开发人员可以像编写同步代码一样编写异步代码。 因此可以使用 try...catch 捕获 async/await 错误。

而这样的函数似乎已经为我们准备好了一切,但是其他刚刚看过你代码的开发者总会有这样的疑问:为什么to函数返回的Promise使用的 await 没有包裹在 try…catch 中?

停止这样使用‘async/await’

只有找到原来的to函数定义,理解它的意图,才能知道“啊,原来to函数返回的Promise永远不会被拒绝”。

因此它进一步增加了其他开发人员的理解成本,使熟悉的 async/await 变得不那么“熟悉”。

2.从实用性的角度出发

to 函数的主要用例是当在同一个上下文中有多个 await Promise 时,它们对应的错误处理是不同的。 然后使用这个封装函数对每个错误进行不同的处理,减少 try…catch 的使用。

但是在实际编写中,每个to函数之后,都需要使用if语句来判断是否有错误。 这与使用 try…catch 的意图没有什么不同,都是为了检查错误。

停止这样使用‘async/await’

其次,在真实的生产环境中,下一个 Promise 依赖上一个 Promise 的情况并不少见。 但重要的一点是,这两个 Promise 通常是关联函数。 所以在外层使用 try...catch 来统一处理错误是没有问题的。 例如:

停止这样使用‘async/await’

最后,在 JavaScript 中,大部分 Promise 场景都在 Input/output 上,比如网络 IO 和文件 IO。 这些IO场景可以在底层封装拦截器,根据错误码统一处理。 例如使用 axios 拦截器:

停止这样使用‘async/await’

所以它可能不像预期的那样实用。 也就是说,它可能只用于整个项目的一小部分,并且成本大于收益。

这仅是我的观点,大家怎么看?