Promise 调用 async 函数内代码时候的错误和解决方案

异步编程是现代 Web 开发中的一个重要主题。JavaScript 提供了多种异步编程模式,在其中,Promise 是一种广泛使用的模式之一。它使得我们可以避免回调地狱,提高代码的可读性和可维护性。而 async/await 则更进一步地简化了异步代码的书写。本文讨论在使用 Promise 调用 async 函数内部代码时可能出现的错误,并提供解决方案。

问题描述

看下面一个简单的例子,使用 Promise 调用一个 async 函数,并在其内部调用另一个 async 函数:

----- -------- ------------ -
  ------ --- --------------- -- ------------- -- ----------------- ------
-

----- -------- ------------ -
  ----- ------ - ----- ------------
  ------------------- -----------
  ------ -------
-

------------------------ -- -------------------- ------------

这段代码输出的结果应该是:

------ -----
------- -----

事实上,大多数情况下,代码运行结果确实如此。但是,当 innerAsync 函数内部抛出异常时,会发生什么呢?我们来修改一下代码,让 innerAsync 抛出一个异常:

----- -------- ------------ -
  ------ --- ----------------- ------- -- ------------- -- ---------------- ------
-

----- -------- ------------ -
  --- -
    ----- ------ - ----- ------------
    ------------------- -----------
    ------ -------
  - ----- ------- -
    --------------------- ----------
    ------ -------
  -
-

------------------------ -- -------------------- ------------

这段代码会输出:

------ -----
------- -----

我们可以看到, innerAsync 函数内部抛出的异常被 outerAsync 函数中的 catch 块捕获了,并返回了一个 error 字符串。但是,由于 Promise 的特性,错误的传播没有停止。实际上,我们可以发现控制台上有一条未捕获的错误栈信息:

这条信息表明我们没有对 innerAsync 函数抛出的错误做出响应,这可能会引起潜在的问题。

解决方案

出现上述问题的原因是在内部 async 函数中 throw 的错误从 Promise 里泄漏出来。在其外部的 try-catch 块中抓住这些错误并不够。我们还需要确保只要异步操作出了问题,我们的 Promise 链就中断。

一种解决方案是将 innerAsync 函数中的 Promise 对象从外部创建,提供一个 catch 块:

-------- ------------ -
  ------ --- ----------------- ------- -- -
    ------------- -- ---------------- -----
  -------------- -- -
    --------------------- ----------
    ----- -----
  --
-

----- -------- ------------ -
  --- -
    ----- ------ - ----- ------------
    ------------------- -----------
    ------ -------
  - ----- ------- -
    --------------------- ----------
    ------ -------
  -
-

------------------------ -- -------------------- ------------

这段代码的输出结果是:

------ -----
------ ---------
------- -----

现在,innerAsync 函数内部的错误被正常捕获和处理,Promise 链被正确地中断,并且我们在未捕获错误处得到了更有用的错误栈信息。即使 innerAsync 函数不使用 async/await,这种做法也是适用的。

除了 catch 内部抛出错误外,在 async 函数内使用 throw 抛出异常,然后在异步操作的 Promise 链中的 catch 中重新抛出该异常也会引起类似的问题。同样地,以下代码也需要进行相应的修改:

----- -------- ------------ -
  ------ --- ----------------- ------- -- -
    ------------- -- ---------------- -----
  --
-

----- -------- ------------ -
  --- -
    ----- ------ - ----- ------------
    ------------------- -----------
    ----- --- --------------
  - ----- ------- -
    --------------------- ----------
    ----- -----
  -
-

------------------------ -- ------------------- -----------

在 innerAsync 函数中引入 catch 块或者在 Promise 上使用 done() 方法可以实现相同的效果。但是这些方法有它们各自的弊端。

  • catch 块中,任何抛出的错误都会被视为 Promise 链中的正常返回值,并将 Promise 链继续传递下去。这可能会不期望地影响 Promise 链中异常的处理。例如,考虑 Promise.all() 中的情况。实际上,一个 Promise 被视为 rejected 后,Promise.all() 立即返回。因此,最好在 Promise.all() 之前检查所有 Promise 的 resolved/rejected 状态,并在出现错误时中断 Promise 链。
  • done() 方法与 Promise 具有相同的 API,不同之处在于该方法不返回 Promise 对象,而是在抛出异常时直接抛出该异常。因此,虽然该方法可以避免适用 catch 块时的误解,但在使用时必须格外小心。

无论使用哪种方法,都要确保在异步操作时出现的任何错误都能够被正确处理和传递。

结论

在使用 Promise 调用 async 函数内部代码时,错误可能会沿着 Promise 链泄漏出来,导致高级错误处理方式失效。为了避免这种情况,我们应该在异步操作的 Promise 链中正确地中断错误传播,并确保所有错误都得到正确处理。以上所述的解决方案是可行的选项之一。我们需要谨慎地思考每个异步操作所必须的异常处理机制,并根据情况进行自定义处理。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/670b9dfd66ef9cf37faa86f3