简体   繁体   中英

Javascript, Promises and setTimeout

I have been playing with Promises, but I am having trouble understanding what is happening with the following code:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 10)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')

setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

The output is:


Promise started - Async code started 
Promise made - Sync code terminated 
Promise log inside first setTimeout 
Promise log inside second setTimeout 
Promise log after fulfilled

It is as expected.

But let check the output of the below code:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 1)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')
setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

Changed the to be resolved promise setTimeout timer value from 10ms to 1ms

The output is:

Promise started - Async code started 
Promise made - Sync code terminated 
Promise log after fulfilled 
Promise log inside first setTimeout 
Promise log inside second setTimeout 

Any explanation for this?

From Concurrency model and the event loop

  • setTimeout does not run immediately after its timer expires
  • Zero delay doesn't actually mean the call back will fire-off after zero milliseconds. Calling setTimeout with a delay of 0 (zero) milliseconds doesn't execute the callback function after the given interval. Basically, the setTimeout needs to wait for all the code for queued messages to complete even though you specified a particular time limit for your setTimeout.

What happen if we set 2 and 1 milliseconds:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 2)
})

console.log('Promise log inside first setTimeout 1')
setTimeout(() => {
  console.log('Promise log inside first setTimeout 2')
}, 1)

promise.then(res => {
  console.log('Promise log after fulfilled ❌')

})

console.log('Promise log inside second setTimeout 1')
setTimeout(() => {
  console.log('Promise log inside second setTimeout 2')
}, 1)
});

The output always will be:

Promise started - Async code started
Promise log inside first setTimeout 1
Promise log inside second setTimeout 1
Promise log inside first setTimeout 2
Promise log inside second setTimeout 2
Promise log after fulfilled ❌

Conclusion

If you want a proper behavior, worth to get rid of Zero delays.

I will use the following example to explain:

setTimeout(() => {
  console.log('1 ms timeout');
}, 1);                            // Moved to async queue at time = T0
setTimeout(() => {
  console.log('0 ms timeout')
}, 0);                            // Moved to async queue after 1 ms that synchronous call to setTimeout takes i.e. at T1
                                  // So at T1, queue will be [("1ms timeout", 0), ("0ms timeout", 0)]

Hence this will print

1 ms timeout
0 ms timeout

Understanding of above: Calling setTimeouts is synchronous (even though its callback is put in async queue), ie we call setTimeout() and move to next statement - this synchronous action itself may take 1ms.

In other words, 1ms is too low a time so by the time JS engine sees the 2nd async statement, the first one has already spent 1ms in the queue.

I also suggest you try out the following

setTimeout(() => {
  console.log("First");
}, 2);                      // queue at T0 = [("First", 2)]

const forLoopLimit = 100;
for (var i = 0; i < forLoopLimit; i++){
    console.log(i * 10000);
}                           // Assume that it takes about 3 milliseconds
                            // queue at T3 = [("First", 0)]
setTimeout(() => {
  console.log("Second");
}, 0);                      // Assume it takes 0 milliseconds.
                            // queue at T4 = [("First", 0), ("Second", 0)]

This will print First before Second even though the former had 2ms timeout compared to the latter having 0ms. Now change forLoopLimit to 1 or even 10, you'll see that the synchronous task doesn't take 3 milliseconds now, and Second is printed before First

Also worth trying is:

console.log(Date.now());
console.log(Date.now());

Try above multiple times and you'll see that sometimes console logs will have different timestamps. Roughly, you can say console.log() and Date.now() take 0.5ms. It's nothing but the time to call / execute synchronous stuff.

Chrome has an hardcoded minimum timeout of 1ms .

  base::TimeDelta interval_milliseconds =
      std::max(base::TimeDelta::FromMilliseconds(1), interval);

So for Chrome, all your setTimeout( fn, 0 ) are converted to setTimeout( fn, 1 ) , and thus scheduled to fire after the first one you did schedule (remember Promise constructors are called synchronously).

So we could actually simplify your example with

 setTimeout( () => console.log( '1ms delay' ), 1 ); setTimeout( () => console.log( '0ms delay' ), 0 );

and in Chrome , the 1ms delay will always fire first, contrary to the common sense because internally it will actually be:

 setTimeout( () => console.log( '1ms delay' ), Math.max(1, 1) ); setTimeout( () => console.log( '0ms delay' ), Math.max(1, 0) );

If you had it set to 1 and 2 instead of 0 and 1 , your expectation would be fulfilled.

 const promise = new Promise((resolve, reject) => { console.log('Promise started - Async code started') setTimeout(() => { resolve('Success') }, 2) }) setTimeout(() => { console.log('Promise log inside first setTimeout') }, 1) promise.then(res => { console.log('Promise log after fulfilled') }) console.log('Promise made - Sync code terminated') setTimeout(() => { console.log('Promise log inside second setTimeout') }, 1)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM