0

I want to know what happens in this code-snippet with the chain of .thens with regards to the stack, the WebAPI section and the micro-task queue. I tried it in Loupe but it didn't seem to register the .thens.

setTimeout(function() {console.log(2)}, 0);

Promise.resolve(1)
.then((value) => {console.log(value)}) //
.then((value) => {console.log(value)}) //

It's the fact that the .thens have to wait til the previous .then returns a resolved promise (.then chaining) that confuses me. I'm not sure how this looks. My guess would be:

The first (and only) time the script is run, it recognises .then as an asynchronous function returning a promise - so it does that in the WebAPI section. The promise that the first .then takes (Promise.resolve1) is synchronous and resolves pretty immediately so the .then's callback ((value) => {console.log(value)}) is placed pretty immediately into the micro-task queue. Perhaps the script sees that there is a .then chain and leaves the rest of them since they all rely on each other.

There's nothing more to look at and 'main'/the script is popped off the stack. Now the callback that had been waiting in the micro-task queue is pushed onto the stack and executed. Great, the callback logs 5 and the callback helps the first .then to return a fulfilled promise with undefined as its 'result'. The second .then is immediately called on this new promise. I'm going to stop here because I don't feel confident.

It would be great to have a step-by-step explanation of what happens, with regard to the call-stack, the WebAPI 'thread' or 'area' and the micro-stack queue.

tonitone120
  • 1,920
  • 3
  • 8
  • 25

1 Answers1

2

The callback helps the first .then to return a fulfilled promise with undefined as its 'result'. The second .then is immediately called on this new promise.

No. The .then(…) methods are both called during the initial execution of the main script, and both do immediately return a new promise (that is however not yet resolved). A promise is just a normal object and can be passed around and logged like any other value, the script executes the method calls normally.

What the .then(…) invocations do beside returning the new promise is to register the callback(s) as fulfillment/rejection handlers on the original promise - without executing them. (When the promise is already fulfilled, like in the example you gave, it also immediately schedules the handlers to run, putting them in the microtask queue).

After the first handler is executed on a microtask and has logged the number, its return value is used to resolve the promise that .then() had returned. Any handlers registered on that promise will be scheduled to run. So when the first microtask is completed, a second one will already be waiting in the queue and execute the second handler with the result value, making it log undefined, and then the last promise in the chain is resolved - but there are no more handlers to schedule, and the microtask queue will be empty.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I'm with you until...the first handler's return value resolves the promise that the 2nd `.then` takes. What if this return value on that first handler was a promise that took a long time? Technically, the 1st handler has popped off the stack. And the stack is empty. But even though the stack is empty, it won't give control to back to the event loop (to do rendering or push on other macro-tasks)? It'll still wait for the `.then` chain to finish? – tonitone120 Aug 26 '20 at 19:14
  • 1
    If the return value of the first handler is an unresolved promise, the promise returned by the `.then(…)` will be [*resolved* with that return value but not *fulfilled*](https://stackoverflow.com/a/29269515/1048572), so no handlers are scheduled and the microtask queue stays empty. Then *when* the inner promise fulfills later, the outer promise will be fulfilled as well and the second handler will be scheduled to run. – Bergi Aug 26 '20 at 19:22
  • Right, and during the time it takes for the inner promise to run, does the event loop wait before pushing on macro-tasks or rendering? Because on the one hand, I read that after a macro-task, everything in micro-task queue is deal with and emptied before handing control back to event loop. On the other hand, I read that a `.then` chain is completed before handing control back to the event-loop – tonitone120 Aug 26 '20 at 19:45
  • 1
    "*I read that a .then chain is completed before handing control back to the event-loop*" - not sure where you read that, but it's wrong (if "completed" means "wait until the last promise fulfills"). if all the handlers in the chain are synchronous, they keep queuing tasks to the microtask queue for sure, but normally `then` chains are asynchronous. – Bergi Aug 26 '20 at 19:50
  • In summary: `.then` chaining doesn't mean you wait until each promise fulfils, putting callbacks in the micro-task queue, before you return control back to the event loop (for it to do rendering or push macro-tasks). (Though synchronous handlers would add micro-tasks onto the micro-task queue in such a way that the micro-task queue doesn't become empty?) And finally, whenever the micro-task queue is empty, control is given back to event loop. If the micro-task queue becomes populated while a macro-task is being executed, its guaranteed to execute immediately after that macro-task? – tonitone120 Aug 26 '20 at 20:10
  • 1
    @tonitone120 Yes. – Bergi Aug 26 '20 at 20:40