4

I was learning promises in JS and got curious on how promises work with Job queues behind the scenes. To explain my confusion I want to show you this code:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

})

If you look at the above code, is it true that the callback of then() is put into Job queue beforehand and waits for promise to resolve? Or Is it true that callback of then() is pushed into job queue only after promise gets resolved?

wewq
  • 229
  • 3
  • 11
  • Note: Don't use `alert` for things like this. `alert`'s behavior is non-standard and varies across browsers (or even with the same browser depending on whether the tab is active or not). – T.J. Crowder Jan 21 '20 at 19:05

2 Answers2

6

When it's time to call a promise callback, the job doesn't go on the standard job queue (ScriptJobs) at all; it goes on the PromiseJobs queue. The PromiseJobs queue is processed until it's empty when each job from the ScriptJobs queue ends. (More in the spec: Jobs and Job Queues.)

I'm not sure what output you were expecting from your code as you didn't say, but let's take a simpler example:

console.log("top");
new Promise(resolve => {
    setTimeout(() => {
        console.log("timer callback");
    }, 0);
    resolve();
})
.then(() => {
    console.log("then callback 1");
})
.then(() => {
    console.log("then callback 2");
});
console.log("bottom");

The output of that, reliably, is:

top
bottom
then callback 1
then callback 2
timer callback

because:

  1. The ScriptJobs job to run that script runs
  2. console.log("top") runs
  3. The promise executor function code runs, which
    • Schedules a timer job for "right now," which will go on the ScriptJobs queue either immediately or very nearly immediately
    • Fulfills the promise (which means the promise is resolved before then is called on it) by calling resolve with no argument (which is effectively like calling it with undefined, which not being thenable triggers fulfillment of the promise).
  4. The first then hooks up the first fulfillment handler, queuing a PromiseJobs job because the promise is already fulfilled
  5. The second then hooks up the second fulfillment handler (doesn't queue a job, waits for the promise from the first then)
  6. console.log("bottom") runs
  7. The current ScriptJob job ends
  8. The engine processes the PromiseJobs job that's waiting (the first fulfillment handler)
  9. That outputs "then callback 1" and fulfills the first then's promise (by returning)
  10. That queues another job on the PromiseJobs queue for the callback to the second fulfillment handler
  11. Since the PromiseJobs queue isn't empty, the next PromiseJob is picked up and run
  12. The second fulfillment handler outputs "then callback 2"
  13. PromsieJobs is empty, so the engine picks up the next ScriptJob
  14. That ScriptJob processes the timer callback and outputs "timer callback"

In the HTML spec they use slightly different terminology: "task" (or "macrotask") for ScriptJobs jobs and "microtask" for PromiseJobs jobs (and other similar jobs).

The key point is: All PromiseJobs queued during a ScriptJob are processed when that ScriptJob completes, and that includes any PromiseJobs they queue; only once PromiseJobs is empty is the next ScriptJob run.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Hi, ok I got that thenable callback is sent to job queue when promise resolves. But the question is why to send thenable callback after that callback gets necessary data from promise? hope you got my point:) – wewq Jan 21 '20 at 18:49
  • @wewq - I'm sorry, I don't understand the question. – T.J. Crowder Jan 21 '20 at 19:04
  • @k1an - When an edit is rejected, don't make the same edit again please. There are no promise reactions attached to the promise at the point it is fulfilled, which which means there are no promise reaction jobs to queue. But thanks for trying to keep things accurate! :-) – T.J. Crowder Feb 27 '21 at 17:15
  • The part after `then` is the callback which gets added to the micro queue and since this queue has higher priority then the macro queue (which holds the setTimeout), hence the setTimeout's console.log happens last? – variable Feb 18 '22 at 11:16
  • @variable - That's right, yes. – T.J. Crowder Feb 18 '22 at 14:30
1

I would say callback of then() is pushed into job queue only after promise gets resolved.

If you changed the first timeout to 3000, you run the code and it waits until 3's to alert 1. This is because you have to wait the promise to be resolved in 3 seconds.

You get get the answer from here: https://stackoverflow.com/a/30910084/12733140

promiseA.then()'s callback is a task

  • promiseA is resolved/rejected: the task will be pushed into microtask queue in current round of event loop.
  • promiseA is pending: the task will be pushed into microtask queue in the future round of event loop(may be next round)

So here microtask is the same as "job" as you mentioned above, only the promise is resolved or rejected, the callback will be pushed to job/microtask queue.

Community
  • 1
  • 1
Limboer
  • 373
  • 4
  • 24
  • hi, ok thenable callback is executed after promise is resolved. But why? since we get data after promise is resolved then why need to send thenable callback to job queue with received data from promise. Hope you got what I mean – wewq Jan 21 '20 at 19:03
  • 1
    Sorry i am actually not sure what you wanna ask. But JavaScript is a single thread language, it only has one thread which is main thread to execute sync code, async callback have to be pushed into task queue first and will be called only until the main thread is empty. You can try pass 0 to settimeout function, and the callback will still be called after the sync stuff are done in the main thread. This is basically how event loop works. There is a classic video on YouTube introducing event loop as well if you are interested to search it. Hope this is what you are asking about. – Limboer Jan 21 '20 at 19:13