0

I'm working on some event loop knowledge refinement and I'm a bit stumped at the following code block:

new Promise((resolve) => {
  console.log('in here, 1')
  setTimeout(() => resolve(3),0);
  console.log('in here, 2')
}).then((val) => console.log(val));

new Promise((resolve) => {
  console.log('in here, 5')
  resolve(4)
  console.log('in here, 6')
}).then((val) => console.log(val));

//in here, 1
//in here, 2
//in here, 5
//in here, 6
//4
//3

I understand that the Promise constructor returns a new Promise instance synchronously; that the code inside of the executor function in a Promise constructor runs synchronously; that .then() returns a new Promise instance synchronously; but that the onFulfilled and onRejected handlers in a .then((onFulfilled, onRejected) => {}) are invoked asynchronously.

I have a few questions regarding the resolve within the setTimeout in the first promise. The callback function in the setTimeout will resolve the promise with the value of 3. However, since callbacks from promises are placed in the microtask queue, and in this case, the .then() will place a (val) => console.log(val) on the microtask queue, why can it read the value of the resolved promise it's attached to? The resolve is in the timers callback queue which hasn't been processed by the event loop yet because we're inside of the microtask queue. Thus, shouldn't the value read by the .then() callback in the first promise be undefined? I may be misunderstanding how resolve exactly works in the executor in the constructor.

I could use some clarification on this and maybe on how the microtask queue works in Node. Thank you.

lewislin
  • 21
  • 2
  • 1
    The `.then()` callback function isn't called until the corresponding `resolve()` is called. At that time, it receives the argument that was passed to `resolve()`. – Barmar Feb 24 '23 at 19:48

2 Answers2

0

the .then() will place a (val) => console.log(val) on the microtask queue

No, it won't. The .then() method does that only for already-settled promises. But if the promise has not yet been resolved, all it does is to register the handler callback in a list on the promise object, to be fired when the promise is fulfilled.

Why can it read the value of the resolved promise it's attached to? Shouldn't the value read by the .then() callback in the first promise be undefined?

There seems to be a misunderstanding here that then somehow "reads" a value. It can't, when the value is not available yet, and it doesn't actively "wait" for the value either. Just like when using any asynchronous callback, it registers a handler to be called later by the code that provides the result - in the case of an pending promise, it is the resolve(3) call that fulfills the promise and places the tasks to run the promise handlers on the microtask queue. This scheduling does not happen until resolve is actually called, which in your example occurs in a function on the timers queue, but it might happen at any time later or even never.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

The then callback is only queued once the promise on which it is called is fulfilled. So in the first block of your code this is not happening immediately, but as the promise implementation logic receives the value to resolve with, it can queue the job for executing the callback and the argument to pass to that callback.

Here is a simplified time line of what happens in terms of callstack, actions (both JS and non-JS) and queues:

JS call stack Action macrotask queue promise job queue
script new Promise: constructor creates promise 1 and calls the given callback - -
script > promise constructor callback console.log outputs "in here, 1" - -
script > promise constructor callback setTimeout registers the given callback - -
script > promise constructor callback console.log outputs "in here, 2" - -
script new Promise() evaluates to the created promise 1 (pending) - -
script .then creates promise 2 and registers the given callback - -
script .then() evaluates to promise 2 (ignored, pending) - -
script new Promise: constructor creates promise 3 and calls the given callback - -
script > promise constructor callback console.log outputs "in here, 5" - -
script > promise constructor callback resolve fulfils promise 3 with value 4 - -
script > promise constructor callback console.log outputs "in here, 6" - -
script new Promise() evaluates to the created promise 3 (fulfilled) - -
script .then creates promise 4 and seeing promise 3 is fulfilled it puts the given callback in the promise job queue with 4 as bound argument - resolve promise 4 using (val=4) => console.log(val)
script .then() evaluates to promise 4 (pending, ignored) - resolve promise 4 using (val=4) => console.log(val)
- JS engine monitors the job queues - resolve promise 4 using (val=4) => console.log(val)
job The job resolve promise 4 using (val=4) => console.log(val) is pulled and executed - -
job console.log(4) outputs "4" - -
job Promise 4 is fulfulled with value undefined - -
- JS engine monitors the job queues - -
- setTimeout implementation detects timer expired (this could have happened earlier, but the effect is the same) - -
- setTimeout implementation puts a job in a macrotask queue () => resolve(3) -
- JS engine monitors the job queues () => resolve(3) -
- JS engine pulls () => resolve(3) from macrotask queue and executes it - -
job resolve(3) fulfils promise 1 to value 3 - -
job Promise implementation puts then callback in promise job queue with 3 as bound argument - resolve promise 2 using (val=3) => console.log(val)
- JS engine monitors the job queues - resolve promise 2 using (val=3) => console.log(val)
job The job resolve promise 2 using (val=3) => console.log(val) is pulled and executed - -
job console.log(3) outputs "3" - -
job Promise 2 is fulfulled with value undefined - -
- JS engine monitors the job queues - -
trincot
  • 317,000
  • 35
  • 244
  • 286