是否可以在异步 Javascript 代码中 "bulletproof" try..catch?
Is it possible to "bulletproof" a try..catch in async Javascript code?
最近我在调试一段异步 NodeJS 代码时遇到了一个非常令人沮丧的时间:我认为肯定会在 try..catch
中捕获的异常泄漏了,导致外部未处理的 promise 错误async_foo
函数。
async function async_foo() {
try {
await some_library.async_bar('some illegal argument');
} catch (err) {
console.error(err); // <- Whether this is called depends on async_bar's implementation !
}
}
从那以后,我了解到在异步 JS 中有许多方法可以搬起石头砸自己的脚,因为 async..await 是如何通过 Promises 实现的,但仍然:
是否有可能以一种绝对肯定总是处理嵌套异步代码错误的方式编写异步 JS 代码,不管嵌套的异步代码是如何实现的?基于库的解决方案很重要。
Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented?
不,而且可能永远不会。 Node.js documentation 解释得很好:
By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where it left off", without leaking references, or creating some other sort of undefined brittle state. The safest way to respond to a thrown error is to shut down the process.
图书馆负责解决每种类型的错误并安全地处理它们并在它们发生时将它们传播(如有必要)给消费者。
您可以使用已弃用的 domain
module,但在这种情况下它可能对您没有帮助,因为“不会为未处理的 Promise 拒绝发出 'error'
事件。”。
Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented? A library-based solution would count.
不,不是。编写不当的异步代码可能会抛出一个普通的异步回调,该回调由事件循环从空堆栈帧调用,您无法使用 try/catch
捕获任何内容。这是一个简单的例子:
setTimeout(() => {
throw new Error("thrown from a setTimeout callback");
}, 10);
除了全局使用类似以下内容之外,无法从回调本身外部捕获该异常:
process.on('uncaughtException', function(err) {
// log and shut down
});
但到那时,您对实际发生的事情或如何修复内部状态一无所知。可能有文件或套接字保持打开状态,可能有事件处理程序。可以分配其他资源。内部状态可能会以糟糕的方式留下。此时通常的建议是 shut-down 服务器并重新启动。
实际上,你不想到这里。您希望通过知道如何正确清理和处理错误的代码在上下文中捕获错误,坦率地说,确实没有任何替代品。
幸运的是,使用 promises 和 .then()
和 .catch()
或 await
和 try/catch
正确编写的代码会将被拒绝的 promises 传播到调用链直到你想要它去。但是,要做到这一点,必须正确编写代码。没有链接或 return promises 的编写不当的代码仍然会造成异常或拒绝处理不当的情况,即使在使用 promises 时也是如此。
最近我在调试一段异步 NodeJS 代码时遇到了一个非常令人沮丧的时间:我认为肯定会在 try..catch
中捕获的异常泄漏了,导致外部未处理的 promise 错误async_foo
函数。
async function async_foo() {
try {
await some_library.async_bar('some illegal argument');
} catch (err) {
console.error(err); // <- Whether this is called depends on async_bar's implementation !
}
}
从那以后,我了解到在异步 JS 中有许多方法可以搬起石头砸自己的脚,因为 async..await 是如何通过 Promises 实现的,但仍然:
是否有可能以一种绝对肯定总是处理嵌套异步代码错误的方式编写异步 JS 代码,不管嵌套的异步代码是如何实现的?基于库的解决方案很重要。
Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented?
不,而且可能永远不会。 Node.js documentation 解释得很好:
By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where it left off", without leaking references, or creating some other sort of undefined brittle state. The safest way to respond to a thrown error is to shut down the process.
图书馆负责解决每种类型的错误并安全地处理它们并在它们发生时将它们传播(如有必要)给消费者。
您可以使用已弃用的 domain
module,但在这种情况下它可能对您没有帮助,因为“不会为未处理的 Promise 拒绝发出 'error'
事件。”。
Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented? A library-based solution would count.
不,不是。编写不当的异步代码可能会抛出一个普通的异步回调,该回调由事件循环从空堆栈帧调用,您无法使用 try/catch
捕获任何内容。这是一个简单的例子:
setTimeout(() => {
throw new Error("thrown from a setTimeout callback");
}, 10);
除了全局使用类似以下内容之外,无法从回调本身外部捕获该异常:
process.on('uncaughtException', function(err) {
// log and shut down
});
但到那时,您对实际发生的事情或如何修复内部状态一无所知。可能有文件或套接字保持打开状态,可能有事件处理程序。可以分配其他资源。内部状态可能会以糟糕的方式留下。此时通常的建议是 shut-down 服务器并重新启动。
实际上,你不想到这里。您希望通过知道如何正确清理和处理错误的代码在上下文中捕获错误,坦率地说,确实没有任何替代品。
幸运的是,使用 promises 和 .then()
和 .catch()
或 await
和 try/catch
正确编写的代码会将被拒绝的 promises 传播到调用链直到你想要它去。但是,要做到这一点,必须正确编写代码。没有链接或 return promises 的编写不当的代码仍然会造成异常或拒绝处理不当的情况,即使在使用 promises 时也是如此。