promise 的意外 unhandledRejection 事件确实得到处理
Unexpected unhandledRejection event for promise which rejection does get handled
已更新,我现在已经在 Reddit 上尝试 , but it'd still be great to have an answer from a credible source about the unhandledRejection
behavor. I've also started a discussion thread。
为什么我会在以下代码中收到 unhandledRejection
事件(对于“error f1”)?这是出乎意料的,因为我在 main
.
的 finally
部分处理了两次拒绝
我在 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}`));
你应该使用 try...catch
来捕获你的 try
块中发生的所有错误:
try {
await p2;
// do something after p2 is settled
await p1;
// do something after p1 is settled
}
catch(e) {
// do something with errors e
}
编辑:
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 main() {
try {
const p1 = await f1();
await delay(200);
}
catch(e) {
console.warn(`caught inside main: ${e.message}`);
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
好的,自言自语。我误解了 unhandledrejection
事件的实际运作方式。
我来自 .NET,如果任务仍未被观察到,失败的 Task
对象 can remain unobserved until it gets garbage-collected. Only then UnobservedTaskException
将被触发。
JavaScript 承诺的情况有所不同。被拒绝的 Promise
没有 已附加的拒绝处理程序(通过 then
、catch
、await
或 Promise.all/race/allSettle/any
),需要一个尽早,否则unhandledrejection
事件可能被触发。
什么时候 unhandledrejection
会被解雇,如果有的话?这好像真的是implementation-specific。关于“未处理的承诺拒绝”的 W3C 规范 do not strictly specify 当用户代理通知拒绝的承诺时。
为了安全起见,我会在当前函数将执行控制权交给调用者之前同步附加处理程序(通过 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
。我只能推测,这可能会发生,因为已解决承诺的延续发布为 microtask:
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)is consistent 与浏览器行为。
现在,下面的 会 触发 unhandledrejection
事件。同样,我可以推测这是因为 await
延续处理程序现在异步附加到 p1
并且在事件循环的某些后续迭代中,当处理 task (macrotask) 队列时:
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}`));
我没有来源,但我认为它是这样工作的:
Promise.reject(new Error("Rejected!"));
return 是一个被拒绝的承诺,下一个 tick 将出错。
所以:
async function main3() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately and attach the await to the promise (so it will not be rejected)
await p1;
}
然后 Promise.resolve
将 return 其结果发送给所有 .then
处理程序下一个 tick(我们没有它们,因为不会将结果存储在任何地方)
所以:
async function main() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately (and would give its value next tick)
await Promise.resolve();
//then this will run immediately and attach the await to the promise
await p1;
}
最后,延迟为 0 的 setTimeout
不会立即触发,检查:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop 并阅读 0 延迟部分
所以:
async function main2() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//setTimeout does with 0 does run not immediately.
//because of this the await p1 does not get added before the promise is rejected
await new Promise(r => setTimeout(r, 0));
//so this does nothing and the prosime will reject
await p1;
}
已更新,我现在已经在 Reddit 上尝试 unhandledRejection
behavor. I've also started a discussion thread。
为什么我会在以下代码中收到 unhandledRejection
事件(对于“error f1”)?这是出乎意料的,因为我在 main
.
finally
部分处理了两次拒绝
我在 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}`));
你应该使用 try...catch
来捕获你的 try
块中发生的所有错误:
try {
await p2;
// do something after p2 is settled
await p1;
// do something after p1 is settled
}
catch(e) {
// do something with errors e
}
编辑:
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 main() {
try {
const p1 = await f1();
await delay(200);
}
catch(e) {
console.warn(`caught inside main: ${e.message}`);
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
好的,自言自语。我误解了 unhandledrejection
事件的实际运作方式。
我来自 .NET,如果任务仍未被观察到,失败的 Task
对象 can remain unobserved until it gets garbage-collected. Only then UnobservedTaskException
将被触发。
JavaScript 承诺的情况有所不同。被拒绝的 Promise
没有 已附加的拒绝处理程序(通过 then
、catch
、await
或 Promise.all/race/allSettle/any
),需要一个尽早,否则unhandledrejection
事件可能被触发。
什么时候 unhandledrejection
会被解雇,如果有的话?这好像真的是implementation-specific。关于“未处理的承诺拒绝”的 W3C 规范 do not strictly specify 当用户代理通知拒绝的承诺时。
为了安全起见,我会在当前函数将执行控制权交给调用者之前同步附加处理程序(通过 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
。我只能推测,这可能会发生,因为已解决承诺的延续发布为 microtask:
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)is consistent 与浏览器行为。
现在,下面的 会 触发 unhandledrejection
事件。同样,我可以推测这是因为 await
延续处理程序现在异步附加到 p1
并且在事件循环的某些后续迭代中,当处理 task (macrotask) 队列时:
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}`));
我没有来源,但我认为它是这样工作的:
Promise.reject(new Error("Rejected!"));
return 是一个被拒绝的承诺,下一个 tick 将出错。
所以:
async function main3() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately and attach the await to the promise (so it will not be rejected)
await p1;
}
然后 Promise.resolve
将 return 其结果发送给所有 .then
处理程序下一个 tick(我们没有它们,因为不会将结果存储在任何地方)
所以:
async function main() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately (and would give its value next tick)
await Promise.resolve();
//then this will run immediately and attach the await to the promise
await p1;
}
最后,延迟为 0 的 setTimeout
不会立即触发,检查:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop 并阅读 0 延迟部分
所以:
async function main2() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//setTimeout does with 0 does run not immediately.
//because of this the await p1 does not get added before the promise is rejected
await new Promise(r => setTimeout(r, 0));
//so this does nothing and the prosime will reject
await p1;
}