What Is the Order of Execution in JavaScript Promises

What is the order of execution in JavaScript promises?

Comments

First off, running promises inside of a .then() handler and NOT returning those promises from the .then() callback creates a completely new unattached promise sequence that is not synchronized with the parent promises in any way. Usually, this is a bug and, in fact, some promise engines actually warn when you do that because it is almost never the desired behavior. The only time one would ever want to do that is when you're doing some sort of fire and forget operation where you don't care about errors and you don't care about synchronizing with the rest of the world.

So, all your Promise.resolve() promises inside of .then() handlers create new Promise chains that run independently of the parent chain. With actual asynchronous operations, you do not have a determinate behavior with non-connected, independent promise chains. It's kind of like launching four ajax calls in parallel. You don't know which one will complete first. Now, since all your code inside those Promise.resolve() handlers happens to be synchronous (since this isn't real world code), then you might get consistent behavior, but that isn't the design point of promises so I wouldn't spend much time trying to figure out which Promise chain that runs synchronous code only is going to finish first. In the real world, it doesn't matter because if order matters, then you won't leave things to chance this way.

Summary

  1. All .then() handlers are called asynchronously after the current thread of execution finishes (as the Promises/A+ spec says, when the JS engine returns back to "platform code"). This is true even for promises that are resolved synchronously such as Promise.resolve().then(...). This is done for programming consistency so that a .then() handler is consistently called asynchronously no matter whether the promise is resolved immediately or later. This prevents some timing bugs and makes it easier for the calling code to see consistent asynchronous execution.

  2. There is no specification that determines the relative order of setTimeout() vs. scheduled .then() handlers if both are queued and ready to run. In your implementation, a pending .then() handler is always run before a pending setTimeout(), but the Promises/A+ spec specification says this is not determinate. It says that .then() handlers can be scheduled a whole bunch of ways, some of which would run before pending setTimeout() calls and some of which might run after pending setTimeout() calls. For example, the Promises/A+ spec allows .then() handlers to be scheduled with either setImmediate() which would run before pending setTimeout() calls or with setTimeout() which would run after pending setTimeout() calls. So, your code should not depend upon that order at all.

  3. Multiple independent Promise chains do not have a predictable order of execution and you cannot rely on any particular order. It's like firing off four ajax calls in parallel where you don't know which one will complete first.

  4. If order of execution is important, do not create a race that is dependent upon minute implementation details. Instead, link promise chains to force a particular execution order.

  5. You generally do not want to create independent promise chains within a .then() handler that are not returned from the handler. This is usually a bug except in rare cases of fire and forget without error handling.

Line By Line Analysis

So, here's an analysis of your code. I added line numbers and cleaned up the indentation to make it easier to discuss:

1     Promise.resolve('A').then(function (a) {
2 console.log(2, a);
3 return 'B';
4 }).then(function (a) {
5 Promise.resolve('C').then(function (a) {
6 console.log(7, a);
7 }).then(function (a) {
8 console.log(8, a);
9 });
10 console.log(3, a);
11 return a;
12 }).then(function (a) {
13 Promise.resolve('D').then(function (a) {
14 console.log(9, a);
15 }).then(function (a) {
16 console.log(10, a);
17 });
18 console.log(4, a);
19 }).then(function (a) {
20 console.log(5, a);
21 });
22
23 console.log(1);
24
25 setTimeout(function () {
26 console.log(6)
27 }, 0);

Line 1 starts a promise chain and attached a .then() handler to it. Since Promise.resolve() resolves immediately, the Promise library will schedule the first .then() handler to run after this thread of Javascript finishes. In Promises/A+ compatible promise libraries, all .then() handlers are called asynchronously after the current thread of execution finishes and when JS goes back to the event loop. This means that any other synchronous code in this thread such as your console.log(1) will run next which is what you see.

All the other .then() handlers at the top level (lines 4, 12, 19) chain after the first one and will run only after the first one gets its turn. They are essentially queued at this point.

Since the setTimeout() is also in this initial thread of execution, it is run and thus a timer is scheduled.

That is the end of the synchronous execution. Now, the JS engine starts running things that are scheduled in the event queue.

As far as I know, there is no guarantee which comes first a setTimeout(fn, 0) or a .then() handler that are both scheduled to run right after this thread of execution. .then() handlers are considered "micro-tasks" so it does not surprise me that they run first before the setTimeout(). But, if you need a particular order, then you should write code that guarantees an order rather than rely on this implementation detail.

Anyway, the .then() handler defined on line 1 runs next. Thus you see the output 2 "A" from that console.log(2, a).

Next, since the previous .then() handler returned a plain value, that promise is considered resolved so the .then() handler defined on line 4 runs. Here's where you're creating another independent promise chain and introducing a behavior that is usually a bug.

Line 5, creates a new Promise chain. It resolves that initial promise and then schedules two .then() handlers to run when the current thread of execution is done. In that current thread of execution is the console.log(3, a) on line 10 so that's why you see that next. Then, this thread of execution finishes and it goes back to the scheduler to see what to run next.

We now have several .then() handlers in the queue waiting to run next. There's the one we just scheduled on line 5 and there's the next one in the higher level chain on line 12. If you had done this on line 5:

return Promise.resolve(...).then(...)

then you would have linked these promises together and they would be coordinated in sequence. But, by not returning the promise value, you started a whole new promise chain that is not coordinated with the outer, higher level promise. In your particular case, the promise scheduler decides to run the more deeply nested .then() handler next. I don't honestly know if this is by specification, by convention or just an implementation detail of one promise engine vs. the other. I'd say that if the order is critical to you, then you should force an order by linking promises in a specific order rather than rely on who wins the race to run first.

Anyway, in your case, it's a scheduling race and the engine you are running decides to run the inner .then() handler that's defined on line 5 next and thus you see the 7 "C" specified on line 6. It then returns nothing so the resolved value of this promise becomes undefined.

Back in the scheduler, it runs the .then() handler on line 12. This is again a race between that .then() handler and the one on line 7 which is also waiting to run. I don't know why it picks one over the other here other than to say it may be indeterminate or vary per promise engine because the order is not specified by the code. In any case, the .then() handler in line 12 starts to run. That again creates a new independent or unsynchronized promise chain line the previous one. It schedules a .then() handler again and then you get the 4 "B" from the synchronous code in that .then() handler. All synchronous code is done in that handler so now, it goes back to the scheduler for the next task.

Back in the scheduler, it decides to run the .then() handler on line 7 and you get 8 undefined. The promise there is undefined because the previous .then() handler in that chain did not return anything, thus its return value was undefined, thus that is the resolved value of the promise chain at that point.

At this point, the output so far is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined

Again, all synchronous code is done so it goes back to the scheduler again and it decides to run the .then() handler defined on line 13. That runs and you get the output 9 "D" and then it goes back to the scheduler again.

Consistent with the previously nested Promise.resolve() chain, the the schedule chooses to run the next outer .then() handler defined on line 19. It runs and you get the output 5 undefined. It is again undefined because the previous .then() handler in that chain did not return a value, thus the resolved value of the promise was undefined.

As this point, the output so far is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined

At this point, there is only one .then() handler scheduled to be run so it runs the one defined on line 15 and you get the output 10 undefined next.

Then, lastly, the setTimeout() gets to run and the final output is:

1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined
10 undefined
6

If one were to try to predict exactly the order this would run in, then there would be two main questions.

  1. How are pending .then() handlers prioritized vs. setTimeout() calls that are also pending.

  2. How does the promise engine decide to prioritize multiple .then() handlers that are all waiting to run. Per your results with this code it is not FIFO.

For the first question, I don't know if this is per specification or just an implementation choice here in the promise engine/JS engine, but the implementation you reported on appears to prioritize all pending .then() handlers before any setTimeout() calls. Your case is a bit of an odd one because you have no actual async API calls other than specifying .then() handlers. If you had any async operation that actually took any real time to execute at the start of this promise chain, then your setTimeout() would execute before the .then() handler on the real async operation just because the real async operation takes actual time to execute. So, this is a bit of a contrived example and is not the usual design case for real code.

For the second question, I've seen some discussion that discusses how pending .then() handlers at different levels of nesting should be prioritized. I don't know if that discussion was ever resolved in a specification or not. I prefer to code in a way that that level of detail does not matter to me. If I care about the order of my async operations, then I link my promise chains to control the order and this level of implementation detail does not affect me in any way. If I don't care about the order, then I don't care about the order so again that level of implementation detail does not affect me. Even if this was in some specification, it seems like the type of detail that should not be trusted across many different implementations (different browsers, different promise engines) unless you had tested it everywhere you were going to run. So, I'd recommend not relying on a specific order of execution when you have unsynchronized promise chains.


You could make the order 100% determinate by just linking all your promise chains like this (returning inner promises so they are linked into the parent chain):

Promise.resolve('A').then(function (a) {
console.log(2, a);
return 'B';
}).then(function (a) {
var p = Promise.resolve('C').then(function (a) {
console.log(7, a);
}).then(function (a) {
console.log(8, a);
});
console.log(3, a);
// return this promise to chain to the parent promise
return p;
}).then(function (a) {
var p = Promise.resolve('D').then(function (a) {
console.log(9, a);
}).then(function (a) {
console.log(10, a);
});
console.log(4, a);
// return this promise to chain to the parent promise
return p;
}).then(function (a) {
console.log(5, a);
});

console.log(1);

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

This gives the following output in Chrome:

1
2 "A"
3 "B"
7 "C"
8 undefined
4 undefined
9 "D"
10 undefined
5 undefined
6

And, since the promise have all been chained together, the promise order is all defined by the code. The only thing left as an implementation detail is the timing of the setTimeout() which, as in your example, comes last, after all pending .then() handlers.

Edit:

Upon examination of the Promises/A+ specification, we find this:

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

....

3.1 Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled and onRejected execute asynchronously, after the event
loop turn in which then is called, and with a fresh stack. This can be
implemented with either a “macro-task” mechanism such as setTimeout or
setImmediate, or with a “micro-task” mechanism such as
MutationObserver or process.nextTick. Since the promise implementation
is considered platform code, it may itself contain a task-scheduling
queue or “trampoline” in which the handlers are called.

This says that .then() handlers must execute asynchronously after the call stack returns to platform code, but leaves it entirely to the implementation how exactly to do that whether it's done with a macro-task like setTimeout() or micro-task like process.nextTick(). So, per this specification, it is not determinate and should not be relied upon.

I find no information about macro-tasks, micro-tasks or the timing of promise .then() handlers in relation to setTimeout() in the ES6 specification. This is perhaps not surprising since setTimeout() itself is not part of the ES6 specification (it is a host environment function, not a language feature).

I haven't found any specifications to back this up, but the answers to this question Difference between microtask and macrotask within an event loop context explain how things tend to work in browsers with macro-tasks and micro-tasks.

FYI, if you want more info on micro-tasks and macro-tasks, here's an interesting reference article on the topic: Tasks, microtasks, queues and schedules.

execution order finally() in promises

All of the then, catch and finally methods are "per promise", the promise they were called on. They all return a new promise that will resolve after the callback(s) have run, and they do not know about any other promises further down the chain (or even in other chains).

If you want your finally callback to run after all the promises in your chain have settled, you should also chain it in the end.

Execution order of Javascript promises when different promise chains attached

First off, when you do this:

promise.then(...).then(...)

That attaches one .then() handler to promise and the second .then() is attached to a new promise that the first .then() returns.

So, when promise resolves, the first .then() is scheduled to fire on the next tick of the event loop. Then, only when the first .then() finishes, it schedules the second .then() to fire on the next tick of the event loop (it does not run on this tick of the event loop).

When you do this:

promise
.then(() => console.log('2'))
.then(() => console.log('3'));

promise
.then(() => console.log('4'))
.then(() => console.log('5'));

And, then when promise resolves, then(2) and then(4) which are the only two .then() handlers in this piece of code directly attached to promise are scheduled to run on the next tick of the event loop. Upon that next tick, first then(2) runs and then then(4) runs. Since they were both attached to the same promise, they run in a FIFO manner, the first one to be attached runs first. When then(2) runs, it schedules then(3) to run on the next tick of the event loop and when then(4) runs, it schedules then(5) to run on the next tick of the event loop.

So, after both then(2) and then(4) have run, upon the next tick of the event loop, you will see then(3) and then(5) run and that explains the sequencing you see in the console of:

1
2
4
3
5

I presume you're not surprised that then(1) runs first so I didn't add that to the explanation.

How is this not executed in order they are being registered to the main promise?

The three .then() handlers attached directly to promise do run in the order they were attached. What is apparently confusing you is that the chained .then() handlers are not attached directly to promise, but to new promises that promise.then() returns.

.then() returns a new promise because it may not resolve immediately. If the callback inside of .then() itself returns a promise, then the promise that .then() returns does not immediately resolved. It has to wait until the promise that the .then() callback returned also resolves.

In my mind, then API adds all the callbacks to an array or something like that, from 1 to 5 and once res is called it starts calling callbacks from the array one by one (FIFO) which should produces logs 1 2 3 4 5 not 1 2 4 3 5.

Well: 1, 2 and 4 are indeed added to the same array of .then() handler callbacks for the promise object. But, as explained above, 3 and 5 are on separate promises that were created by the .then() they are chained to. So, 1, 2 and 4 are executed FIFO. But 3 and 5 are delayed to a subsequent tick of the event loop.

Order of execution of branching Promises

In this case (-> calling .then() multiple times on the same promise)...

const p = new Promise((resolve, reject) => { setTimeout(resolve, 1000) });

p.then(() => console.log("first"));
p.then(() => console.log("second"));

order of execution in async/await

There's no microtask involved in the execution of the specific sequence you asked about (from * to **), although a microtask gets queued during that sequence (then run later).

An async function is synchronous until the first await, uncaught exception, or return (including an implicit return where code execution just "falls off" the end of the function). At that point, the function returns a promise and synchronous execution continues from the point at which the function was called. If it was an exception or a return or implicit return (not an await), a microtask is queued to settle the promise from the function (rejecting it on exception or fulfilling it on return).

I've highlighted the synchronous parts of that code in yellow:

The code from the question, with highlighting on: 1. async1 and async2's empty parameter lists, the first console.log in each, and (in async1) the call to async2() but not the await on it; 2. The two statements at the end that aren't in any function.

In your example, when you call async1 at the bottom, its synchronous portion runs (logging Async 1 and then calling async2, which logs FROM async 2 during its synchronous portion); having run async2 and gotten a promise from it, async1 reaches the await and returns its own promise; then synchronous execution continues from just after you called it, logging Synchronous.

In this particular example, between the * and ** parts, a microtask for the fulfillment of async2's promise is queued. When the task executing the synchronous code in your example is done, that microtask is picked up from the microtask queue and executed — and that settles the promise from async1 that was waiting for it; async1's code continues, does its log, and then implicitly returns, so a microtask is queued to process that settlement (fulfillment in this case), and picked up from the microtask queue once the async2 microtask is done executing. But nothing is waiting for that promise, so the settlement doesn't have any apparent effect.

Execution order of Timeout and Promise functions(Main Tasks and Micro Tasks)

I think output one and four are pretty clear. setTimeout is a part of Main Task queue while Promise is of Micro task queue that's why "three" and finally "two" is printed.

Step by Step execution is as below:

  1. When script is executed, console.log(“one”) is executed first.

  2. When setTimeout(…) is encountered, runtime initiates a timer, and after 0ms. Callback function() {} of setTimeout(…) is queued in Task queue.

  3. When promise object is encountered, its callback i.e function() {} is queued in Micro task queue.

  4. Finally, it executes last console.log(“four”) .

According to standard specification

  1. Once a call stack is emptied, it will check Micro task queue and finds callback function() {} of promise.Call stack will execute it (logs three).

  2. Once again, call stack is emptied after processing callbacks in Micro task queue. Finally, event loop picks up a new task from the Task queue i.e callback function() {} of setTimeout(…) ( logs two) execute it.


Visual Image

Visual Image

console.log('one');
setTimeout(function() {
console.log('two');
}, 0);
Promise.resolve().then(function() {
console.log('three');
})
console.log('four');

Execution order of Promises in Node.js

the setImmediate() call shows us, that no console.log() calls get deferred to the next Event Loop iteration

Actually they do - remember that promise then callbacks are always asynchronous.

They just do get run on a different phase of the event loop, they get queued on different task queues. For NodeJS specifically, see the article https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ for details.

I expected following output

Don't assume anything about independent promise chains. All you can and should expect is that B comes after A, C comes after B, and in the other chain that E comes after D and F after E. They are allowed to be interleaved arbitrarily1 - if you want to ensure order, chain the promises to each other using then.

1: the spec details how the queuing of promise tasks works so that it is consistent between different engine implementations, but those details should be considered irrelevant.



Related Topics



Leave a reply



Submit