Search code examples
javascriptes6-promise

How to accurately know/judge the execution order of Promise in JavaScript ?


I wonder why the execution order is "0 1 2 3 4 5 6" instead of "0 1 4 2 3 5 6".

Promise.resolve().then(() => {
  console.log(0)
  return Promise.resolve(4)
}).then((res) => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1)
}).then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
}).then(() => {
  console.log(5)
}).then(() => {
  console.log(6)
})


Solution

  • It happens that way because it takes the JavaScript interpreter multiple microtasks to unwrap the value (4) from a returned promise.

    That's because promises are built in a way that most of the time only one thing happens in each microtask. A notable example is that then callbacks always run in a new microtask.

    The second chain of promises is executed alternately with the first chain. Its presence doesn't change the result, but it highlights how many steps (microtasks) it takes for something to happen.

    In this case, the following happens (disregaring the second chain of promises here):

    • The then callback logging 0 runs, and returns the promise resolved to 4.
    • The then method of the returned promise is called internally to extract its value.
    • The then callback is called and receives the value 4. The first promise is now fulfilled, it schedules its then callback to be executed.
    • The last then callback is called, receiving and logging the value 4.

    So, in total, it takes 4 microtasks to print the value 4, which matches what we see.

    You can observe this behavior by returning a custom thenable object, that forwards its operations to a real promise (I changed the logged values here to better illustrate what's going on):

    Promise.resolve().then(() => {
      console.log('#1')
    }).then(() => {
      console.log('#2')
    }).then(() => {
      console.log('#3')
    }).then(() => {
      console.log('#4')
    }).then(() => {
      console.log('#5')
    }).then(() => {
      console.log('#6')
    })
    
    Promise.resolve().then(() => {
      console.log(1)
    
      const returnValue = Promise.resolve(4)
    
      return {
        then(rs, rj){
          // This is "inside" `Promise.resolve(4).then()`
          console.log(2)
          returnValue.then(v => {
            //This is "inside" the callback given by the interpreter 
            console.log(3)
            rs(v)
          })
        }
      }
    }).then((res) => {
      console.log(4)
    })