Search code examples
javascriptsettimeoutes6-promiseevent-loop

Explain this order of execution of setTimeout and catch handlers


I have this doubt regarding order of execution of Timer functions , microtasks and event listeners :

let promise = Promise.reject("Err@!");

setTimeout(() => {
        promise.catch(er => console.log(er, "Caught !"));         // (1)
});

window.addEventListener('unhandledrejection', event => console.log("Unhandled Rejection !", event.reason));  //  (2)

// Output 
// >> Unhandled Rejection ! Err@!
// >> Err@! Caught !

In this, the rejected promise gets caught by Unhandled Rejection event before being caught by .catch() inside setTimeout, the reason for that is:

An "unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue (https://javascript.info/microtask-queue#unhandled-rejection)

Now Considering another case :

let promise = Promise.reject("Err@!");

setTimeout(() => {       // (1)  ,gets called first

    setTimeout(function () {  // (3) , gets called at last , (after (2))
        promise.catch(er => console.log("caught ", er  ))
    })

});

setTimeout(function () {      // (2) , gets called after (1)
    window.addEventListener('unhandledrejection', event => console.log("Unhandled Rejection !", event.reason));
})

// Output 
//>>  caught  Err@!

In this, the promise gets caught by catch handler in nested setTimeout, even when in second setTimeout, the microtask queue is empty and the rejected promise is still not handled by window event listener...

Why so?


Solution

  • By the time the promise rejects (without being handled), the unhandledrejection handler was not attached yet, thus it does not handle the case. You can attach .catch and .then handlers whenever you want, and if the Promise already resolved/rejected, they will fire immeadiately (in a microtask).

     // first example
     1) synchronous execution
      - promise rejected
      - timeout set 
      - unhandledrejection event handler attached
     2) microtasks
      - promise rejects
     3) macrotask
      - timeout triggers
        -> .catch handler gets attached
     4) microtasks
       .catch gets executed
    
     // second example
     1) synchronous execution
     - timers get attached
     - promise rejects
     2) microtasks
     - promise rejected
     3) macrotasks
     - timer 1 done
       - timer 3 started
     - timer 2 done 
        - unhandledrejection handler added