异步编程是现代 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