I’m confused about the execution order of the following code (Ran on jsfiddle):
let num = 10;
window.addEventListener("unhandledrejection", (event) => {
num = 20
});
let promise = new Promise((resolve, reject) => {
reject("reject");
});
setTimeout(() => {
promise.catch(() => {
console.log(num);
});
}, 1);
Now depending on the delay of the setTimeout function, the output changes.
For delays < 1 ms the output is:
10
Even after running the code, the num variable is still 10 as if the unhandledrejection event wasn’t triggered.
For delays >= 1ms the output is:
20
I am not sure how the difference in delay affects the output. A detailed step by step explanation on the order of execution is appreciated.
2
Answers
both promise and setTimeout works in async manner so even if you are do just reject in this promise it will popped out of js execution order so it will technically take something more time then "0ms" to throw error
now if you put 0 ms in timeout , it will register catch handler befor promise throws error so window event wont be listened so num === 10
but if you put more then 0ms in settimeout , by the time promise throws error , there is not catch handler is registered so window event listener sets num = 20
Fundamentally, what you’re seeing is down to:
unhandledrejection
event, which will be host-specific. The specification that applies to browers just says it’s fired "…when a promise rejection goes unhandled…" but in practice there’s a fair bit of room for implementation-specific heuristics in there. Some environments may schedule it the first time they go through their event loop after a rejection has occurred that hasn’t (yet) been handled. Others may wait a couple of loops, to support situations like yours where you do add a reject handler, you just add it a couple of async ticks late.setTimeout
. The specification that applies to browers allows for host-determined artificial delays.In practical terms: If you want to avoid unhandled rejection errors, hook up handlers early rather than late. It’s not generally useful in practical terms to get too caught up in the exact details of race conditions like this, particularly when host-specific operations are involved. Separately, if you’re handling
unhandledrejection
, you probably also want to handlerejectionhandled
.Your experiment tells you that in the environment where you ran it, if you set the timer value low enough, you add the rejection handler to the promise before the environment schedules the
unhandledrejection
event, but if you set it a bit longer, you don’t, so the event gets fired. Here’s an example showing the individual parts more explicitly:You either do or don’t see
"unhandled rejection"
(followed later by"previously-unhandled rejection handled"
) depending on the timer value and host environment’s heuristics around unhandled rejections andsetTimeout
.