skip to Main Content

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


  1. 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

    Login or Signup to reply.
  2. Fundamentally, what you’re seeing is down to:

    1. When the host environment triggers the 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.
    2. The host’s interpretation of very low timer values for 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 handle rejectionhandled.

    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:

    window.addEventListener("unhandledrejection", () => {
        console.log("unhandled rejection");
    });
    window.addEventListener("rejectionhandled", () => {
        console.log("previously-unhandled rejection handled");
    });
    
    document.getElementById("btn").addEventListener("click", () => {
        console.clear();
        
        const delay = document.getElementById("delay").valueAsNumber;
        console.log(`delay = ${delay}`);
    
        const promise = new Promise((resolve, reject) => {
            reject("reject");
        });
    
        setTimeout(() => {
            console.log("adding rejection handler");
            promise.catch(() => {
                console.log("done");
            });
        }, delay);
    });
    <input id="delay" type="number" value="1" min-value="0">
    <input id="btn" type="button" value="Run">

    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 and setTimeout.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search