How do promise chains start and finish - javascript

I'm a little confused about how the sequencing works in various documents I've come across. For instance, I have seen this sort of thing
let p = Promise.resolve();
for (let x in something)
{
/* do some hairy time consuming code */
p = p.then(dosomething(x));
}
return p.then(finalthing()).catch(pretendnobadthing());
What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.
Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point? And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?

Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point?
Yes, you can always use .then/.catch on a promise. When you call p.then(), there are three possibilities.
The promise p is still pending (not fulfilled or rejected yet). If that's the case, then the function reference(s) you passed to .then() are registered as listeners for that promise. So, when a future state transition happens on the promise (either going from pending => fulfilled or from pending => rejected), then the appropriate registered listeners will be called.
The promise p is already fulfilled. If that's the case, then calling .then(f1, f2) will schedule f1 to be called on the next tick (after the current piece of Javascript finishes executing) and it will be passed the saved resolved value.
The promise p is already rejected. If that's the case, then calling .then(f1, f2) will schedule f2 to be called on the next tick (after the current piece of Javascript finishes executing) and it will be passed the saved reject reason.
So, it's perfectly safe to call .then() on a promise that is already fulfilled or rejected. The appropriate listener will just be scheduled to run on the next tick.
The same logic applies to .catch() except it is only interested in cases 1 and 3 above.
Here are
And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?
Promises are just objects like any other objects in Javascript. They will hang around only while other code still has some live reference to them. As soon as there is no longer any way to reach that promise object, they will be eligible for garbage collection just like any other objects in Javascript.
So, if you do:
var p = somePromiseReturningFunction();
p.then(f1).then(f2).then(f3);
Then, p will remain around until somePromiseReturningFunction() is done with any references to the promise that it returned (usually, though not always, this occurs when the promise is finally fulfilled or rejected) and when the variable p goes out of scope. If p never goes out of scope (like when it's global or in some other lasting scope), then it will remain forever (just like any other Javascript object).
There are some misconceptions in your question so let me attempt to square those up.
You're using the construct p = p.then(dosomething(x)); which is likely not correct. You need to pass .then() a function reference. So, unless you want doSomething(x) to execute immediately and it also returns another function that is what you want called as then .then() handler (which seems unlikely here), then this is not the right construct. You probably meant to have:
p = p.then(result => dosomething(x));
or in ES5 syntax:
p = p.then(function(result) {
return dosomething(x)
});
You show the same issue in this too:
return p.then(finalthing()).catch(pretendnobadthing());
which should probably be:
return p.then(finalthing).catch(pretendnobadthing);
Remember, when you use f(), that means to execute f immediately. When you just pass f, that passes a function reference which the underlying function/method you are passing it to can then call later at the time of its choosing which is what you want for .then() and .catch() handlers.
What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.
First off, my original explanation at the beginning of my answer should explain that calling .then() on an already resolved promise is perfectly fine so this isn't an issue at all. It will just schedule action on the next tick of the event loop.
But, that isn't even the case here because Javascript in the browser and node.js is single-threaded so while your long-running code is running, that promise (who's async action was previously started) can't yet get resolved. Though the underlying async operation may be done and an event may be sitting in the Javascript event queue that will trigger a callback that will resolve the promise, that event in the event queue won't get processed until the current piece of Javascript that is executing is done and returns control to the system.

What I really don't understand is that if you do something that takes a large amount of time in the main code, wouldn't that mean that one of the promises could complete before the main code actually got round to to setting up the .then.
Since common implementations of JavaScript never run it in parallel, no. Nevertheless,
Can you always use .then/.catch on a promise, and if its already reached completion, it'll carry on from the point it got to, so it conceptually looks like a big chain that will run to completion at some indeterminate point?
yes! This is a big advantage of promises. Take this for example:
var promise = Promise.resolve(5); // already resolved here
setTimeout(function () {
promise.then(function (x) {
console.log(x); // still logs 5
});
}, 1000);
And if so, doesn't that mean every time you create a promise chain it'll hang about for ever?
Until it’s resolved, yes, but if the promise has already been resolved and there are no ways to reference it anymore, it can be disposed of like any other object.

Related

Are there promise library with serial and resolution without async-await?

The inconvenience I have is that there is no way, to my knowledge, using built-in Promise, to resolve a Promise within a synchronous block.
Specifically, the following code
var o = {};
function set(index, value) {
console.log(`setting ${index} to ${o.index = value}`);
}
Promise.resolve().then(()=>set('a', 1)).then(()=>set('b',2))
Promise.resolve().then(()=>set('c', 1)).then(()=>set('d',2))
console.log(`a is ${o.a}`);
Outputs :
a is undefined
setting a to 1
setting c to 1
setting b to 2
setting d to 2
Are there Promise like lib whose Promise would yield the following outputs:
setting a to 1
setting b to 2
setting c to 1
setting d to 2
a is 1
Obviously, no need to resort to Promises in the example above, but in my use case, setting 'a' leads to a pretreatment which only needs to be done once, whereas 'b' can be set multiple times, and is used within the same synchronous block it is set (no freedom on that).
I've looked up bluebird, and I have no clue how their promises work internally, but they seem to embrace using only promise chains.
Q Promise might be what I'm looking for, I'm not sure.
More generally, it feels daunting scouring the docs of the many Promise librairies out there querying some niche, depreciated need with likely the wrong keywords and concepts in mind.
This is definitely a case of ProblemXY where X could be solved in a variety of ways without Promises, for example by synchronous initializers, but I feel problem Y is interesting in and of itself (and likely a duplicate), so I'm still asking.
I'll get around giving problem X its own question, but long story short, I'd love to use, sometimes within a single js synchronous execution block, the expressive power of promises to pull values instead of pushing them and to flatten callbacks and try-catch pyramids.
Edit:
To clarify what I'm curious about, the way I would build the Promiselike with the desired properties is as follow :
let f = myPasync(function*(){ //equivalent to async function declaration
v = yield myPAwait(myPromiseInstance); //equivalent of v = await myPromiseInstance;
})
let pa = new myP( *(resolve, reject)=>{} ) //equivalent to new Promise((resolve, reject)=>{}) ; for simplicity, resolve and reject can be naked properties of pa
pa.myPthen((*()=>{})()) //equivalent to pa.then((v)=>{})
And then, instead of dynamically iterating over a task queue every tick, pushing thens and awaits of newly resolved promised to that queue in a FIFO manner,
I would push to the task queue in a LIFO manner and iterate over it upon any myP related function call.
So for example, when a promise is resolved, every routine awaiting it will resume before anything else, and in particular before the rest of the routine resolving the promise.
Other example when a routine awaits a promise, if that promise is already resolved, the routine is immediately resumed instead of letting other execute.

Order in Promise/Async/Await (Interview Question)

Question:
new Promise((resolve) => {
console.log(1)
resolve()
}).then(async () => {
console.log(2)
}).then(async () => {
console.log(3)
})
new Promise((resolve) => {
console.log('a')
resolve()
}).then(() => {
console.log('b')
}).then(() => {
console.log('c')
}).then(() => {
console.log('d')
}).then(() => {
console.log('e')
})
Answer:
1
a
2
b
c
d
3
e
But I think answer is.... 1 a 2 b 3 c d e
Please help to share how to think to get answer detailed. Thanks.
First I would explain that nobody should ever rely on precise timing between two separate promise chains. They run independently of one another and as soon as you insert any real world asynchronous operations in either of those promise chains (which all real-world programming would contain), then the timing of each chain is entirely unpredictable vs. the other.
Instead the timing in real code depends upon the timing of the asynchronous operations, not on anything else. Imagine that each of the steps in this promise chain was reading a file or doing some random delay. Those are entirely unpredictable operations so which chain does what first depends upon the timing of the actual asynchronous operations, not on what is shown in this example.
So, trying to dissect the details of exactly when each item goes into the promise job queue and when it gets serviced is a complete waste of programming time and not useful in any real programming problem.
Further, if you really need a specific ordering of operations, then you don't program with two independent promise chains. Instead, you use promise flow-of-control tools (like chaining everything into one chain or using Promise.all(), Promise.race(), etc...) to guide the execution/completion order into exactly what you want it to be regardless of the detailed inner workings of the promise implementation.
Then, I would explain the basics of how the promise queue works by walking through the first two links of one of the promise chains just to show that I understand how a promise gets resolved, gets added to the promise queue and then, when control is about to return to the event loop, the oldest item in the promise queue gets to run and call its .then() or .catch() handlers. This is just to illustrated that you understand the basics of how promises are scheduled and that they get serviced in LIFO order from their own job queue and before most other things in the event loop.
Then, I would explain that a few years ago, the spec was changed for some promise steps in the interest of improving performance and a JS engine before or after that spec change would likely generate different results for something like then. Yet another reason why you shouldn't rely on that level of implementation detail.
If the interviewer insisted on me trying to explain the precise order in this problem and wouldn't listen to my reasoning why that's a pointless exercise, even after hearing my explanation for how I'd code a real world situation where execution order does matter, then unless this was just some really junior interviewer trying to be clever (and outsmarting themselves), I'd have to conclude this is not a good place to work. No senior developer should insist that this is a super valuable or practical exercise beyond showing that you have a basic understanding of how the promise job queue works.

What happens first: setTimeout 0 or await Promise.resolve?

I'm seeing this behavior in Node and Chrome:
setTimeout(()=>{ console.log('timeout') }, 0)
Promise.resolve().then(()=>{ console.log('promise') })
console.log('sync')
// output order:
// sync
// promise
// timeout
My question is, is this consistent behavior? I.e, according to spec, does a then or await on a memoized/already resolved promise always fire before setTimeout(fn, 0)?
I want to use this in something like the following, returning one thing if I have a memoized result in my promise and another if not:
// somewhere during object initialization
this.resultingPromise = expensiveAsyncFunction()
// in a method called frequently
Promise.race([
new Promise(resolve => setTimeout(() => resolve('default'), 0)),
this.resultingPromise
])
Promise.resolve will schedule a microtask while setTimeout schedule a macrotask. And the microtasks will run before running the next macrotask.
More information about event loop in general: https://www.youtube.com/watch?v=8aGhZQkoFbQ
More technical details about events loop: https://www.youtube.com/watch?v=cCOL7MC4Pl0
so you have 2 async waiting States, but notice that one of them is constant and one is changing (variable). The timeout is set in an XML variable aside while the promise could took forever. If I understood your question quite well, when you have something you rely on take too long and something too short, unless you have a constant applied on one of them like the timeout, then one might end up running shorter unexpectedly (!) Be prepared for that and instead use monolithic structure from code security reasons and not performance.
There's no guarantee that one will be before the other. If you want to guarantee the order of execution - use Promises.
From my understanding Promise has a higher priority in the call stack than setTimeout, and of course synchronous code block will be the first to be executed. In that case, yes the observed behaviour above (in the order of synchronous code block, promise.resolve, and setTimeout 0) should be consistent.

What is the order of execution in JavaScript promises?

I'd like to understand the execution order of the following snippet that uses JavaScript promises.
Promise.resolve('A')
.then(function(a){console.log(2, a); return 'B';})
.then(function(a){
Promise.resolve('C')
.then(function(a){console.log(7, a);})
.then(function(a){console.log(8, a);});
console.log(3, a);
return a;})
.then(function(a){
Promise.resolve('D')
.then(function(a){console.log(9, a);})
.then(function(a){console.log(10, a);});
console.log(4, a);})
.then(function(a){
console.log(5, a);});
console.log(1);
setTimeout(function(){console.log(6)},0);
The result is:
1
2 "A"
3 "B"
7 "C"
4 "B"
8 undefined
9 "D"
5 undefined
10 undefined
6
I'm curious about the execution order 1 2 3 7... not the values "A", "B"...
My understanding is that if a promise is resolved the then function is put in the browser event queue. So my expectation was 1 2 3 4 ...
Why isn't 1 2 3 4 ... the logged order?
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
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.
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.
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.
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.
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.
How are pending .then() handlers prioritized vs. setTimeout() calls that are also pending.
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.
The browser's JavaScript engine has something called the "event loop". There is only one thread of JavaScript code running at a time. When a button is clicked or an AJAX request or anything else asynchronous completes, a new event is placed into the event loop. The browser executes these events one at a time.
What you're looking at here is that you run code that executes asynchronously. When the asynchronous code completes, it adds an appropriate event to the event loop. What order the events are added in depends on how long each asynchronous operation takes to complete.
That means that if you're using something like AJAX where you have no control over what order the requests will complete in, your promises can execute in a different order each time.
The HTML event loop contains both various task queues and one microtask queue.
At the beginning of each event-loop's iteration, a new task will be taken from one of the task queues, that's what are colloquially called "macro-tasks".
The microtask-queue however isn't visited only once per event-loop iteration. It is visited every time the JS call-stack is emptied. This means that it can be visited numerous times during a single event-loop iteration (because all tasks executed in an event-loop iteration don't come from a task queue).
One more particularity of that microtask-queue is that microtasks that are queued while the queue is being dequeued will get executed right away in the same checkpoint, without letting the event-loop do anything else.
In your example, everything chained or inside the first Promise.resolve("A") is either synchronous, or queuing a new microtask, without anything actually queuing a (macro)task.
This means that when the Event Loop enters the microtask checkpoint to execute the first Promise reaction callback, it will not leave that microtask checkpoint until the last queued microtask has been executed.
So your timeout is quite irrelevant here, it will get executed after all these Promise reactions.
This being clarified, we can now walk through your code and replace every Promise reaction with the underlying queueMicrotask(callback) it will call. It is then quite clear what is the order of execution:
queueMicrotask(function(a) { // first callback
console.log(2, a, 1);
queueMicrotask(function(a) { // second callback
// new branch
queueMicrotask(function(a) { // third callback
console.log(7, a, 3);
queueMicrotask(function(a) { // fifth callback
console.log(8, a, 5);
});
}.bind(null, "C"));
// synchronous (in second callback)
console.log(3, a, 2);
//main branch
queueMicrotask(function(a) { // fourth callback (same level as third, but called later)
// new branch
queueMicrotask(function(a) { // sixth callback
console.log(9, a, 6);
queueMicrotask(function(a) { // eighth callback
console.log(10, a, 8);
});
}.bind(null, "D"));
// synchronous
console.log(4, a, 4);
// main branch
queueMicrotask(function(a) { // seventh callback
console.log(5, a, 7);
});
}.bind(null, a))
}.bind(null, "B"));
}.bind(null, "A"));
// synchronous
console.log(1);
// irrelevant
setTimeout(function() {
console.log(6);
});
Or if we extract every callbacks outside of the chain:
function first(a) {
console.log(2, a, 1);
queueMicrotask(second.bind(null, "B"));
}
function second(a) {
queueMicrotask(third.bind(null, "C"));
console.log(3, a, 2);
queueMicrotask(fourth.bind(null, a));
}
function third(a) {
console.log(7, a, 3);
queueMicrotask(fifth);
}
function fourth(a) {
queueMicrotask(sixth.bind(null, "D"));
console.log(4, a, 4);
queueMicrotask(seventh);
}
function fifth(a) {
console.log(8, a, 5);
};
function sixth(a) {
console.log(9, a, 6);
queueMicrotask(eighth);
}
function seventh(a) {
console.log(5, a, 7);
}
function eighth(a) {
console.log(10, a, 8);
}
queueMicrotask(first.bind(null, "A"));
Now I should note that dealing with already resolved (or immediately resolved) Promises is not something you should see every day, so beware that as soon as one of these Promise reactions is actually bound to an asynchronous task, the order won't be reliable anymore, moreover since different (macro)task queues may have different priorities defined by the UA.
However, I believe it is still important to understand how the microtask-queue works to avoid blocking the event-loop by expecting Promise.resolve() will let the event loop breath, it won't.

Why does cancelling a bluebird promise in a callback stop setInterval?

Promise = require 'bluebird'
cb = ->
console.log 'callback!'
p = Promise.resolve(5)
.cancellable()
.tap -> p.cancel()
setInterval(cb, 100)
The cb function only is only called once. Commenting out .tap -> p.cancel() allows it to run repeatedly. Adding a try block doesn't help. Perhaps this is something obvious, but I did some research and can't find an explanation.
It would seem that the act of returning the value of p.cancel() from the tap handler is causing bluebird to go into some sort of infinite loop. You never see the second 'callback!' because the execution context is stuck in this loop before 100 ms have elapsed.
I'm still far from understanding all the factors at play here (see below), but it looks like this can be fixed by not returning p.cancel():
Promise = require 'bluebird'
cb = ->
console.log 'callback!'
p = Promise.resolve(5)
.cancellable()
.tap ->
p.cancel()
null
setInterval(cb, 100)
Edit: Ok, after looking at the source and unknotting my brain a few times, I think it boils down to this:
Execution is getting stuck in an infinite loop here, where .cancel() tries to climb up the promise chain:
while ((parent = promiseToReject._cancellationParent) !== undefined &&
parent.isCancellable()) {
promiseToReject = parent;
}
The salient points are the following:
p.cancel() returns p.
.tap() returns a promise that resolves whenever the promise returned from its handler resolves (if it returns a promise)
p is the promise that .tap() returns
In other words, p is a promise that will resolve after p resolves. It is its own ancestor in the promise chain (at least, I think so).
When .cancel() tries to climb up the promise chain to find a cancellable promise, it happens upon this incestuous relationship and starts going in circles forever.
In the end, it's an unfortunate consequence of CoffeeScript's eagerness to turn almost everything into a return statement. But I would imagine that there's some way Bluebird could detect loops in promise chains and prevent an infinite loop from happening here.
I have filed an issue for this on the bluebird GitHub repository, but as the ensuing discussion reveals, this use of .cancel() doesn't really make any sense at all anyway.

Categories