Search code examples
javascriptnode.jspromisesettimeoutes6-promise

NodeJS Promise resolve unexpected behavior


I have the following code, which does not work as I expect.

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const myPromise = new Promise((resolve, reject) =>
    resolve('should be right after baz, before bar')
);
const foo = () => {
    console.log('foo')
    setTimeout(bar, 0)
    myPromise.then(resolve => console.log(resolve))
    baz()
}

foo()
console.log('before bar')

The result:

foo
baz
before bar
should be right after baz, before bar
bar

According to what's written in https://nodejs.dev/ , promise resolve will happen right after the function.

Promises that resolve before the current function ends will be executed right after the current function.

So I expect 'should be right after baz, before bar' to happen right after the end of foo(), before console.log('before bar'). Can someone explain why it doesn't?


Solution

  • Callbacks passed to setTimeout are added in the task queue whereas the callbacks related to promise fulfilment or rejection are added in the micro-task queue.

    These queues are only processed after the synchronous execution of your script, i.e. the javascript code you wrote.

    As a result, 'before bar' is logged before any asynchronous callbacks are invoked.

    How your code executes?

    1. Script execution starts.

    2. Function bar, baz, and foo are defined. Promise constructor is also called which leads to the invocation of resolve function, resolving the promise synchronously

    3. foo function is called

      1. 'foo' is logged on the console
      2. setTimeout is called, scheduling bar to be called asynchronously. bar will be added in the task queue
      3. then method is called on myPromise, scheduling the fulfilment handler to be invoked asynchronously. This fulfilment handler is added in the micro-task queue
      4. baz function is called, logging 'baz' on the console
    4. foo function ends. Control return to the next line from where foo was called

    5. Last console.log statement is executed, logging 'before bar' on the console

    6. Script execution ends

    At this point, console output is as shown below:

    foo
    baz
    before bar
    

    and the task queue contains a task to execute the bar function whereas the micro-task queue contans a micro-task to execute the fulfilment handler of the myPromise.

    After the script execution ends, task and micro-task queues can be processed.

    The micro-task queue is processed:

    • after each callback as long as the call-stack is empty (this is what nodejs.dev is referring to)
    • after each task

    Script execution is a task and after its execution, micro-task queue will be the first one to be processed, logging 'should be right after baz, before bar' on the console.

    At last, the task queue is processed, baz function is called, logging 'baz' on the console.


    Following might be a helpful read to understand the micro-task and the task queue: JavaScript async callbacks - Promise and setTimeout