0

I'd like to know the order in which the JavaScript code below is executed and why. In particular, I would like to learn more about the contents of the code that goes into the microtask queue, focusing on step-by-step

var promise = Promise.resolve();

promise = promise.then(function(rtnVal) {
  console.log('1');
  var promise2 = Promise.resolve();
  promise2 = promise2.then(function(rtnVal) {
    console.log('2');
  });
  promise2 = promise2.then(function(rtnVal) {
    console.log('3');
  });
  console.log('4');
});

promise = promise.then(function(rtnVal) {
  console.log('5');
  var promise2 = Promise.resolve();
  promise2 = promise2.then(function(rtnVal) {
    console.log('6');
  });
  promise2 = promise2.then(function(rtnVal) {
    console.log('7');
  });
  console.log('8');
});

console.log('9');

Here's what I did: 9,1,4,2,5,8,3,6,7 ->I'm wondering why it runs in this order`

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • `promise.then` just schedules something to be done later, when it's convenient. So all of the `then` function return immediately. You do 2 `then` calls in a row, and then synchronously log the `9`. At that point, the queued tasks can start. – Tim Roberts Jun 22 '23 at 02:35
  • When I think about it, the contents of the callback function from console.log ('1') to console.log ('4') are first put into the microtask queue, then 5~8 are put into the microtask queue, and then 9 is taken first, then 1 in the queue, and 2 and 3 are put into the queue again, so it will be 4, then 5,8, 2,3,6,7, and so on, so why not? – Won Jin Kim Jun 22 '23 at 04:14
  • In general, you may want to know ahead what is the order of execution you are looking for _or_ your application requires. This will help write the code with proper constructs. I suggest you study the documentation and try some working examples of using Promises and synchronous code. Also, look into async-await construct and also the callbacks - you can achieve the same functionality with these. – prasad_ Jun 22 '23 at 04:42
  • I would have expected 9, 1, 4, 5, 8, 2, 3, 6, 7, so the only surprise to me is that that the 2 came too early. You're sure that's what your code looks like? – Tim Roberts Jun 22 '23 at 04:56
  • Yes, this is exactly the code that represents the result. 9, 1, 4, 5, 8, 2, 3, 6, 7 Run this code and you'll see it right away. – Won Jin Kim Jun 22 '23 at 05:33
  • Note, these types of discussions are largely meaningless because real-world promises have actual, indeterminate timing and are not all resolved immediately. So, you're analyzing something that has little real-world practical use. And, if you really needed independent promise chains to work in a specific order, you would code them that way (so they weren't independent promise chains). So, while many seem to think this kind of discussion is educational and useful, it doesn't really have much real world use. – jfriend00 Jun 22 '23 at 07:03
  • Thank you for your reply, this code is a bit more complicated than the code I actually use at my company, but it's similar enough to the real code. I use it similarly in my actual work. So, if I could understand the execution order of this code correctly, it would be very helpful for my work, so I made it – Won Jin Kim Jun 22 '23 at 07:33

1 Answers1

1

Some things to be aware of:

  • When a promise is resolved, any then callbacks (if any) are put on the PromiseJob queue
  • When then() is called (not its callback), the returned promise is always pending, even if that then() call is made on a resolved promise. This is because the returned promise can only be resolved by executing the callback that is given to the then method since it determines how the promise will resolve, but which can only execute asynchronously.

To ease the analysis, I modified your script to give a name to each promise (a to i) and to each callback function (a_then, ...), but the logic and output remains the same:

var a = Promise.resolve();

var b = a.then(function a_then() {
  console.log(1);
  var c = Promise.resolve();
  var d = c.then(function c_then() {
    console.log(2);
  });
  var e = d.then(function d_then() {
    console.log(3);
  });
  console.log(4);
});

var f = b.then(function b_then() {
  console.log(5);
  var g = Promise.resolve();
  var h = g.then(function g_then() {
    console.log(6);
  });
  var i = h.then(function h_then() {
    console.log(7);
  });
  console.log(8);
});

console.log(9); 

Here is a sequence of events that happen during the execution of that code.

  • The first column represents what is executing (the main script, a function initiated from the event loop, or the host that dequeues an item from the PromiseJob queue)
  • The second column has the current expression/statement being evaluated
  • The columns a to i represent the state of the promise with that name: ? for pending, F for fulfilled.
  • The last column pictures what is present in the PromiseJob queue, managed by the host

Here we go:

Task Action a b c d e f g h i PromiseJob queue
Script a = Promise.resolve() F - - - - - - - -
Script b = a.then(a_then) F ? - - - - - - - a_then
Script f = b.then(b_then) F ? - - - ? - - - a_then
Script console.log(9) F ? - - - ? - - - a_then
Host dequeue a_then F ? - - - ? - - -
a_then console.log(1) F ? - - - ? - - -
a_then c = Promise.resolve() F ? F - - ? - - -
a_then d = c.then(c_then) F ? F ? - ? - - - c_then
a_then e = d.then(d_then) F ? F ? ? ? - - - c_then
a_then console.log(4) F ? F ? ? ? - - - c_then
a_then return resolves b F F F ? ? ? - - - c_then, b_then
Host dequeue c_then F F F ? ? ? - - - b_then
c_then console.log(2) F F F ? ? ? - - - b_then
c_then return resolves d F F F F ? ? - - - b_then, d_then
Host dequeue b_then F F F F ? ? - - - d_then
b_then console.log(5) F F F F ? ? - - - d_then
b_then g = Promise.resolve() F F F F ? ? F - - d_then
b_then h = g.then(g_then) F F F F ? ? F ? - d_then, g_then
b_then i = h.then(h_then) F F F F ? ? F ? ? d_then, g_then
b_then console.log(8) F F F F ? ? F ? ? d_then, g_then
b_then return resolves f F F F F ? F F ? ? d_then, g_then
Host dequeue d_then F F F F ? F F ? ? g_then
d_then console.log(3) F F F F ? F F ? ? g_then
d_then return resolves e F F F F F F F ? ? g_then
Host dequeue g_then F F F F F F F ? ?
g_then console.log(6) F F F F F F F ? ?
g_then return resolves h F F F F F F F F ? h.then
Host dequeue h_then F F F F F F F F ?
h_then console.log(7) F F F F F F F F ?
h_then return resolves i F F F F F F F F F
Host queue is empty F F F F F F F F F
trincot
  • 317,000
  • 35
  • 244
  • 286
  • I'm completely impressed. I needed such a detailed explanation, but thank you for doing more than that. I will analyze it well and make it very helpful in my work. – Won Jin Kim Jun 22 '23 at 23:45
  • Glad to hear it was useful! If you wish to do so, you can also [mark the answer as accepted](http://stackoverflow.com/help/someone-answers), but it is optional. – trincot Jun 23 '23 at 06:36
  • May I ask one more question? If you add a line called "return e;" to the line below "console.log(4);", the order of 9,1,4,2,5,8,3,6,7 changes to 9,1,4,2,3,5,8,6,7, why is that? Even if there is no statement that returns a promise, it seems that a statement that automatically returns a resolved promise is executed, so shouldn't the result be the same as the first code? – Won Jin Kim Jun 23 '23 at 08:11
  • When a `then` callback returns a value that happens to be a promise (like `e`), a very specific feature of promise chaining kicks in: in your example, `b` still resolves, but it resolves with a promise, which means it is still pending (not fulfilled) and its fate is now linked ("locked-in") with the fate of `e`, and therefore `b_then` will not be queued yet -- as that only happens when `b` is fulfilled. – trincot Jun 23 '23 at 08:48
  • See also [JS Promises: Fulfill vs Resolve](https://stackoverflow.com/a/56850392/5459839) – trincot Jun 23 '23 at 08:51
  • Thank you for quick reply, One more question is allowed? Even if you return c that is already fullfilled, it will be executed in the same order as when returning e or d, in the same order as 9, 1, 4, 2, 3, 5, 8, 6, 7, but is it explained in the same way as you said? In other words, when you do the following. ... console.log(4); return c; ... – Won Jin Kim Jun 23 '23 at 08:58
  • Thank you so much. I'll read what you mentioned carefully. – Won Jin Kim Jun 23 '23 at 09:00
  • When you return a resolved promise in a `then` callback there still are some queued events that get processed before `b_then` gets queued (the table I presented is a simplification of what actually happens). Here we enter in very specific ECMA Script specification on how it should work. You could ask a new question about that, but I should say that you should never write code that depends on that order of events. It should never be necessary. – trincot Jun 23 '23 at 09:08
  • I understand most of it, but there are some things that I still don't quite understand, so I'm going to ask you more. var b = a.then(function a_then() { ... console.log(4); (What a difference) }); Here are (What a difference) part case1) blank case2) return c; case3) return e; I think it would be very helpful to understand if you could just tell me what was different in the event table for each of the three cases, but is it possible? – Won Jin Kim Jun 26 '23 at 01:59
  • I suggest you pick one case and ask a new question about it. The comment section is not really intended for new questions. – trincot Jun 26 '23 at 04:40