更新了,我现在尝试了explaining the behavior I'm seeing,但是从可靠来源获得关于 unhandledRejection
行为的答案仍然很棒。我还在Reddit上开始了discussion thread的发布。
为什么在以下代码中出现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
对象can remain unobserved失败,直到被垃圾回收。如果仍然无法执行任务,则只有 UnobservedTaskException
会被触发。
JavaScript promise 的情况有所不同。尚未附加拒绝处理程序(通过Promise
,then
,catch
或await
)的拒绝的Promise.all/race/allSettle/any
需要尽快安装,否则可能会触发unhandledrejection
事件。
什么时候会精确触发unhandledrejection
?这似乎确实是特定于实现的。当用户代理通知已拒绝的 promise 时,W3C会在“未处理的 promise 拒绝” do not strictly specify上进行规范。
为了安全起见,在当前函数将执行控制放弃给调用者之前,我将同步附加处理程序(通过类似return
,throw
,await
,yield
的操作)。
例如,以下代码不会触发unhandledrejection
,因为await
延续处理程序是在p1
promise被创建为已经被拒绝后立即同步附加到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
。我只能推测,这可能是发生的,因为已解决的promise的延续被发布为microtask:window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await Promise.resolve();
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
带有浏览器行为的Node.js(发布时为v14.14.0)is consistent。
现在,以下确实触发了
unhandledrejection
事件。再次,我可以推测是因为现在在处理task (macrotask)队列时,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
结果。我可以想到许多情况,当我真正想要保留对promise的引用,然后对其进行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}`));