javascript - 未处理拒绝的承诺的意外 unhandledRejection 事件
问题描述
更新了,我现在已经尝试解释我所看到的行为,但是从可靠来源获得有关行为的答案仍然很棒unhandledRejection
。我还在Reddit 上开了一个讨论帖。
为什么我会unhandledRejection
在以下代码中收到事件(对于“错误 f1”)?这是出乎意料的,因为我finally
在main
.
我在 Node (v14.13.1) 和 Chrome (v86.0.4240.75) 中看到了相同的行为:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function f1() {
await delay(100);
throw new Error("error f1");
}
async function f2() {
await delay(200);
throw new Error("error f2");
}
async function main() {
// start all at once
const [p1, p2] = [f1(), f2()];
try {
await p2;
// do something after p2 is settled
await p1;
// do something after p1 is settled
}
finally {
await p1.catch(e => console.warn(`caught on p1: ${e.message}`));
await p2.catch(e => console.warn(`caught on p2: ${e.message}`));
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
解决方案
好的,回答我自己。我误解了unhandledrejection
事件的实际运作方式。
我来自 .NET,其中一个失败的Task
对象在被垃圾收集之前可能不会被观察到。只有这样UnobservedTaskException
才会被解雇,如果任务仍然没有被观察到。
JavaScript Promise 的情况有所不同。没有Promise
附加拒绝处理程序(通过、、或)的被拒绝,需要尽早处理,否则可能会触发事件。then
catch
await
Promise.all/race/allSettle/any
unhandledrejection
什么时候unhandledrejection
会被解雇,如果有的话?这似乎真的是特定于实现的。W3C 关于“未处理的承诺拒绝”的规范没有严格指定 用户代理何时通知拒绝的承诺。
为了安全起见,我会在当前函数将执行控制权交给调用者之前同步附加处理程序(通过类似return
, throw
, await
, 的方式yield
)。
例如,以下内容不会触发unhandledrejection
,因为延续处理程序是在已被拒绝状态下创建承诺之后同步await
附加到的。这就说得通了:p1
p1
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
unhandledrejection
即使我们将await
处理程序附加到p1
异步,以下仍然不会触发。我只能推测,这可能会发生,因为已解决承诺的延续被发布为微任务:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await Promise.resolve(r => queueMicrotask(r));
// or we could just do: await Promise.resolve();
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
Node.js(发布此内容时为 v14.14.0)与浏览器行为一致。
现在,以下确实触发了该unhandledrejection
事件。同样,我可以推测这是因为在处理任务(宏任务)队列时,await
延续处理程序现在p1
异步附加到事件循环的一些后续迭代中:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await new Promise(r => setTimeout(r, 0));
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
我个人觉得这整个行为令人困惑。我更喜欢 .NET 方法来更好地观察Task
结果。我可以想到很多情况,当我真的想保留对一个承诺的引用,然后await
是它,并在稍后的时间线上捕获任何错误,以使其解决或拒绝。
也就是说,有一种简单的方法unhandledrejection
可以在不引起事件的情况下获得此示例所需的行为:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
p1.catch(console.debug); // observe but ignore the error here
try {
await new Promise(r => setTimeout(r, 0));
}
finally {
await p1; // throw the error here
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
推荐阅读
- javascript - 是否将模块中的功能导出到另一个模块中?
- c# - UWP,我的应用程序在构建时因图像路径而崩溃,但是当我在调试器上运行它时它工作正常
- jenkins - 构建步骤“执行 shell”在超时后将构建标记为失败
- ios - 尝试使用 CocoaPods“WeScan”,但我不断收到“No such Module 'WeScan'”错误
- dart - Dart 2:构建符合 CSP 的 JS
- git - 向 GitHub 添加 SSH 密钥的目的是什么?
- arrays - array_upper 和 array_length 之间有什么功能区别吗?
- svg - 如果文本字体大小小于 1,如何呈现文本(在 svg 中)
- android - Android Q Api 29 - 如何从“存储访问框架”获取存储(SD 卡)的大小(总或可用)
- spring-boot - Oauth2:Spring Boot 2:设计:不同的身份验证服务器和资源服务器 - 在哪里存储所有用户信息?