29

I am curious about the relationship between Event Loop and Promise.
The demo exposes the question. I expected the p1 fulfilled appear in the middle, since they queue a task to the same task queue and are executed one by one.

var p1 = new Promise(function(resolve, reject){
    resolve(1)
})
setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop")
},0)
p1.then(function(value){
  console.log("p1 fulfilled")
})
setTimeout(function(){
  console.log("will be executed at the bottom of the next Event Loop")
},0)

The console result is:

p1 fulfilled
will be executed at the top of the next Event Loop
will be executed at the bottom of the next Event Loop

The visualized effect shows the promise.then's callback didn't go to the task queue of the Event Loop. It's right?

【NOTE: The question is not the same as Promise vs setTimeout, since it focus more on the relationship between Event Loop and Promise】

Ye Shiqing
  • 1,519
  • 2
  • 15
  • 24
  • You can imagine a second task queue that is processed at the end of the current [tick](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#process-nexttick) i.e. before anything in the normal task queue. – Tesseract Sep 23 '17 at 03:39
  • 1
    `setTimeout` also has a minimum delay. – zzzzBov Sep 23 '17 at 03:52
  • _"I expected the `p1 fulfilled` appear in the middle."_ Why do you expect that result? – guest271314 Sep 23 '17 at 04:06
  • @torazaburo it's not a duplicate. The question `Promise vs setTimeout` is just a example to reveal the relationship between Event Loop and promise. And I get the super answer I want exactly. Please not flag it as a duplicate – Ye Shiqing Sep 26 '17 at 03:12
  • 1
    @PageYe Then vote to re-open and let the people decide. –  Sep 26 '17 at 03:54

2 Answers2

52

Each event loop has a microtask queue and a macrotask queue.

A microtask is a task that is originally to be queued on the microtask queue rather than a task queue. Refer to https://www.w3.org/TR/html51/webappapis.html#microtask-queue.

There are two kinds of microtasks:

  • solitary callback microtasks, such as Promise
  • and compound microtasks, such as Object.observe, MutationObserver and process.nextTick in Node.js.

And the macrotask queue mainly contains setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O in Nodejs.

In a event Loop, these two task queues will run in two steps:

  1. First, check whether there is a macrotask (call it X) in old macrotask queue ;
  2. If X exists and it is running, wait for it to go to the next step until it was complete; otherwise, goto the next step immediately;
  3. Second, run all microtasks of the microtask queue;
  4. and when run the microtasks, we can still add some more microtaks into the queue, these tasks will also run.

In your example:

  1. First, your Promise initialize new Promise and resolve are synchronous;
  2. and then synchronously add a setTimeout macroTask into the macrotask queue;
  3. then synchronously add the microtask promise.then(function(){}) to the microtask queue, this task will run immediately, because the Promise initialize and resolve are synchronous, this task run before any macrotask; so, console.log the p1 fulfilled;
  4. then add the second macrotask setTimeout to macrotask queue;
  5. after this event loop ended, run the two macrotasks;

for this code:

setTimeout(function() {
  console.log("will be executed at the top of the next Event Loop")
}, 0)
var p1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(1)
  }, 0)
});
setTimeout(function() {
  console.log("will be executed at the bottom of the next Event Loop")
}, 0)
for (var i = 0; i < 100; i++) {
  (function(j) {
    p1.then(function(value) {
      console.log("promise then - " + j)
    });
  })(i)
}

the output order:

will be executed at the top of the next Event Loop
promise then - 0
promise then - 1
promise then - 2
...
promise then - 99
will be executed at the bottom of the next Event Loop
  1. First add three macrotask setTimeout to macrotask queue, and a microtask promise.then() to the microtask queue;
  2. run a macrotask;
  3. If condition true run all microtasks, but it's false, so go to the next step;
  4. run the second macrotask;
  5. check whether the promise resolved or not, the condition is true, then run all microtasks;
  6. go on to run other macrotasks;
fortunee
  • 3,852
  • 2
  • 17
  • 29
JiangangXiong
  • 2,326
  • 1
  • 12
  • 14
  • you say `X` will be checked first, however, is the `setTimeout` macroTask in step 2 the `X`? why not execute it first? – Ye Shiqing Sep 23 '17 at 15:33
  • Because your promise initialize and resolve are synchronous, so when in step one, check whether there is a macrotask (called it X) in old macrotask queue, it will return false. Only after the synchronous code, the `setTimeout` macrotask will be invoked. – JiangangXiong Sep 24 '17 at 02:12
  • Since w3.org say nothing about the macrotask queue, what the difference between macrotask queue and task queue? Conceptually, does the microtask queue a kind of task queue or they are completely different? – Ye Shiqing Sep 26 '17 at 03:36
  • "solitary callback microtasks, such as Promise" Would it be correct to be more specific here, instead stating: "solitary callback microtasks, which can only be created using Promise.prototype.then() or await" – AnonEq Jul 04 '19 at 15:05
1

Promises will not be called unless the stack is clear of application code as per Dr. Axel Rauschmayer here.

... the Promises/A+ specification demands that the latter mode of execution be always used. It states so via the following requirement (2.2.4) for the then() method:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

It's important to note:

That means that you code can rely on run-to-completion semantics (as explained in part 1) and that chaining promises won’t starve other tasks of processing time.

sheeldotme
  • 2,237
  • 14
  • 27
  • 1
    I don't understand how this answers the question. In the OP's example, the `then` clause **was** called while there were pending `setTimeout` tasks, assuming those are considered "application code" or "platform code", which I don't know the definition of--what do they mean? –  Sep 23 '17 at 05:00