What does resolve actually do?
Consider the code below.
It prints : 1 3 4 5 6 9 7 10 11 2.
Irrespective of where resolve is written, it prints the same!
Can someone explain why this happens?
new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
resolve();
new Promise((resolve, reject) => {
console.log(3);
resolve();
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(9);
})
.then(() => {
console.log(10);
})
.then(() => {
console.log(11);
})
;
// resolve()
}).then(() => {
console.log(5);
new Promise((resolve, reject) => {
console.log(6);
resolve();
}).then(() => {
console.log(7);
});
})
What does 'resolve' actually do?
Calling resolve(x) does three things.
It changes the internal state of the promise to fulfilled. Once the state is changed to fulfilled, the promise state cannot be changed again. This is a one-way, permanent change.
It sets the value x (whatever single argument you pass to resolve) as the resolved value of the promise (this is stored internal to the promise). If nothing is passed to resolve(), then the resolved value is undefined.
It inserts an event into the event queue to trigger the .then() handlers of this current promise to get called upon an upcoming cycle through the event loop. This schedules the .then() handlers to run after the current thread of Javascript execution finishes.
I'll explain the sequence you see in the console, but first here are a few points that will help to understand this:
The promise executor function (the callback passed to new Promise(fn)) is called synchronously (in the midst of the current thread of execution).
When a setTimeout() timers fires (internal to the JS engine), the timer callback is inserted in the event queue and will be picked up on a future cycle of the event loop.
When a promise resolves, an event is inserted into the event queue and will be picked up on a future cycle of the event loop.
There are multiple types of event queues in the event loop and not all have the same priority. In general promise events will be picked up before most other types of events though it's possible this can vary a bit according to the implementation. So, when multiple types of events are both put in the event queue so they are in there at the same time, this can affect which one gets called first.
Multiple .then() or .catch() handlers that are added to the event queue are handled in the order (relative to each other) that they were originally triggered in a FIFO basis (first-in-first-out).
When promise chaining with something like fn().then(f1).then(f2).then(f3) keep in mind that each .then() returns a new promise that will have its own time that it gets resolved or rejected, after the one before it and depending upon what happens in its handler function.
So, here's the sequence of events in your code:
The first promise executor function is called, thus you get the output 1
A timer is created with a timeout of 0. At some point very soon, a timer callback event will be added to the event queue.
You call resolve() on that first promise. This inserts an event/task into the promise queue to call its .then() handlers on a future cycle of the event loop. The rest of this sequence of Javascript code continues to execute. But, notice that there are not yet any .then() handlers on that first promise as its chained .then() methods haven't yet been executed.
You create a second promise and its executor function is called immediately which outputs 3.
You call resolve() on that second promise. This inserts an event/task into the promise queue to call its .then() handlers on a future cycle of the event loop. The rest of this sequence of Javascript code continues to execute.
.then() is called on that second promise. This registers a .then() handler callback function in that second promise (adds it to an internal list) and returns a new promise.
.then() is called on that newly returned promise (third promise). This registers a .then() handler callback function in that third promise (adds it to an internal list) and returns a new promise.
.then() is called on that newly returned promise (fourth promise). This registers a .then() handler callback function in that fourth promise (adds it to an internal list) and returns a new promise.
.then() is called on that newly returned promise (fifth promise). This registers a .then() handler callback function in that fifth promise (adds it to an internal list) and returns a new promise.
The executor function from the very first promise finally returns.
.then() is called on the very first promise. This registers a .then() handler callback function in that first promise (adds it to an internal list) and returns a new promise.
Because the .then() handler from the second promise ran before the .then() handler from the first promise, it gets put into the tasks queue first and thus you get the output 4 next.
When this .then() handler runs, it resolves the promise that it created earlier, the third promise and it adds a task to the promise queue to run its .then() handlers.
Now, the next item in the task queue is the .then() handler from the first promise so it gets a chance to run and you see the output 5.
This then creates another new promise with new Promise(...) and runs its executor function. This causes the output 6 to show.
This new promise is resolved with resolve().
Its .then() is called which registers a .then() callback and returns a new promise.
The current sequence of Javascript is done so it goes back to the event loop for the next event. The next thing that was scheduled was the .then() handler for the fourth promise so it gets pulled from the event queue and you see the output 9.
Running this .then() handler resolved the fifth promise and inserts its .then() handler into the promise task queue.
Back to the event queue for the next promise event. There we get the .then() handler from the final new Promise().then() in the code and you get the output 7.
The above process repeats and you see the outputs 11, then 12.
Finally, the promise task queue is empty so the event loop looks for other types of events that aren't as high a priority and finds the setTimeout() event and calls its callback and you finally get the output 2.
So, setTimeout() goes last here for a couple of reasons.
Promise events are run before timer events (in ES6), so any queued promise events are served before any queued timer event.
Because all of your promises resolve without actually having to wait for any other asynchronous events to complete (which is kind of not real-world behavior and not typically how or why one uses promises), the timer has to wait until they're all done before it gets its chance to run.
And, a few other comments:
Figuring out the relative firing order of various .then() handlers in different and independent promise chains is sometimes possible (it's only possible here because there are no real asynchronous promise resolves with uncertain resolution times), but if you really need a specific execution order, then it's better to just chain your operations to explicitly specify the order you want things to run in the code. This removes any dependency on minute implementation details of the local Javascript engine and makes the code a ton more self-explanatory. In other words, someone reading your code doesn't have to go through the 22 steps I listed to follow the desired execution order. Instead, the code will just specify the order by direct promise chaining.
In real code, it's unusual to have the orphaned, disconnected promise chains you create inside .then() handlers. Because you are not returning those promise from the .then() handler and thus inserting them in the parent promise chain, there is no way to communicate results or errors back from those disconnected promises chains. While there is very occasionally a reason to code a fire-and-forget operation that doesn't need to communicate at all with the outside world, that is unusual and is usually a sign of problem code that doesn't properly propagate errors and whose results aren't properly synchronized with the rest of what's going on.
when I place 'resolve' behind, it prints the same!
As you've discovered, this doesn't really change anything. The .then() following the new Promise(...) isn't executed until after the executor function finishes running and returns so it doesn't really matter where inside the executor you call resolve(). Said another way, none of the .then() handlers can even be registered until after the promise executor returns so no matter where you call resolve() in the promise executor, the result is the same.
resolve indicate the completion of asynchronous task.
In the below code,
new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
resolve();
new Promise((resolve, reject) => {
console.log(3);
resolve();
})
.then(() => {
console.log(4);
})
You have created new Promise, and immediately resolved it using resolve(), so it does not wait for setTimeout to get executed. The resolve(); is immediately followed by
new Promise, which creates new Promise followed by the execution of immediate then section.
In .then you have not returned any thing, so your then's are not chained properly. Return the value in then to chain it properly.
new Promise((resolve) => {
console.log("1");
setTimeout(() => {
resolve(2);
});
}).then((val) => {
console.log(val);
return "3";
}).then((val) => {
console.log(val);
return "4";
}).then((val) => {
console.log(val);
});
Related
I've been experimenting with the queueMicrotask() function and I'm not figuring out how callbacks are prioritized when the callbacks are microtasks.
Check out the following code:
function tasksAndMicroTasks() {
const promise = Promise.resolve()
console.log('#1st call')
promise
.then(() => console.log('#3rd call'))
.then(() => console.log('#4th call'))
.then(() => console.log('#5th call'))
queueMicrotask(() => console.log(`I'm microtask from the custom Queue`))
console.log('#2nd call')
}
tasksAndMicroTasks()
then the output is:
#1st call
#2nd call
#3rd call
I'm microtask from the custom Queue
#4th call
#5th call
Then I continue with my experiments and try this:
function tasksAndMicroTasks() {
const promise = Promise.resolve()
console.log('#1st call')
promise
.then(() => console.log('#3rd call'))
promise
.then(() => console.log('#4th call'))
.then(() => console.log('#5th call'))
queueMicrotask(() => console.log(`I'm microtask from the custom Queue`))
console.log('#2nd call')
}
tasksAndMicroTasks()
Then the output is:
#1st call
#2nd call
#3rd call
#4th call
I'm microtask from the custom Queue
#5th call
Therefore my conclusion it is that the "chained promises" are recognized like "second-priority" after the first resolutions and after that the tasks registered using queueMicrotask(callback) API.
Can anyone explain me how the Execution Context Stack handle the chained promises and why the chained ones are differents that the first .then() registration of callback?
A promise returned by .then() is resolved with the result of the callback, so it fulfills only after the callback has been called. Only at this point, the promise handlers of the chained promise are scheduled on the microtask queue - after all the ones that had initially been scheduled on the already fulfilled promise or by queueMicrotask.
Keep in mind that
promise.then(fn1).then(fn2);
is not the same as
promise.then(fn1); promise.then(fn2);
For the purposes of your experiment, Promise.resolve().then(fn) is doing the same thing as queueMicrotask(fn). The Promise is already resolved, so the callback function is queued.
When you chain .then() callbacks, you're adding callbacks to the returned Promises from the calls to .then(). Those Promise objects will not resolve until each .then() resolves in sequence. That's the key difference between your first example and your second. In the second one, you add two .then() callbacks to the same already-resolved Promise, so they're both queued at that point.
Therefore my conclusion it is that the "chained promises" are
recognized like "second-priority"
It's not a difference in priority. If you have the code:
promise.then(a).then(b)
It's not clear until the first promise resolves whether .then(b) will ever be called. The first promise may never resolve.
Therefore .then(b) can't be added to the microtask queue immediately, because it may never need to ever be added to the microtask queue.
Why does a function called after my promise execute before the promise's callback?
I read this in MDN, but didn't understand it
"Callbacks will never be called before the completion of the current
run of the JavaScript event loop."
I thought it meant that if I have any other statements after resolve() or reject() they will get executed before the callback is invoked. Though, that seems to be an incomplete understanding.
function myFunction() {
return new Promise( function(resolve, reject) {
const err = false;
if(err) {
reject("Something went wrong!!!");
}
else {
resolve("All good");
}
});
}
myFunction().then(doSuccess).catch(doError);
doOther();
function doError(err) {
console.log(err);
}
function doSuccess() {
console.log('Success');
}
function doOther() {
console.log("My Other Function");
}
Output:
My Other Function
Success
By specification, a promise .then() or .catch() callback is never called synchronously, but is called on a future tick of the event loop. That means that the rest of your synchronous code always runs before any .then() handler is called.
So, thus your doOther() function runs before either doSuccess() or doError() are called.
Promises are designed this way so that a promise .then() handler will be called with consistent timing whether the promise is resolved immediately or resolved some time in the future. If synchronous .then() handlers were allowed, then calling code would either have to know when it might get called synchronously or you'd be susceptible to weird timing bugs.
In the Promises/A+ specification which the promises in the ES6 specification were based on, it defines a `.then() handler like this:
promise.then(onFulfilled, onRejected)
and then has this to say about it:
2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
And, then it defines platform code like this:
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.
Basically what this means is that .then() handlers are called by inserting a task in the event loop that will not execute until the currently running Javascript finishes and returns control back to the interpreter (where it can retrieve the next event). So, thus any synchronous Javascript code you have after the .then() handler is installed will always run before the .then() handler is called.
I had similar confusions about what it means by executing after the current loop. I was going through MDN docs on Promises, which says the following:
Callbacks added with then() will never be invoked before the completion of the current run of the JavaScript event loop.
This website http://latentflip.com/loupe/ video expains it pretty well, basically api's like setTimeout execute after the inbuilt js functions. But, the problem with callbacks is, if it doesn't use those apis then it might execute before the finish of current run of the event loop. Following examples show the difference:
Old school callbacks
var foo = function(then1) {
console.log("initial");
var i = 0;
while (i < 1000000000) {
i++;
}
then1();
}
function then11() {
console.log("res");
}
foo(then11);
console.log("end"); // unlike Promises, end is printed last
New Promise
var promise = new Promise(function(resolve, reject) {
console.log("Initial");
//while loop to 10000
var i = 0;
while(i<1000000000) { //long async task without ext apis
i++;
}
resolve("res");
});
promise.then(function(result) {
console.log(result); // "Stuff worked!"
});
console.log("end"); // end is printed before res!!!
I have a function that does a bunch of work then returns a resolved Promise. Is there a difference in the timing when each will be resolved?
Option 1
function returnPromise() {
// do a bunch of stuff
return Promise.resolve();
}
and this way:
Option 2
function returnPromise() {
return new Promise((resolve, reject) => {
//do a bunch of stuff
resolve();
});
}
Does Option 1 release control after all the work is done, and Option 2 release control as soon as the function is called? I feel like this has root in the fact I don't fully understand the event loop. Thanks in advance for helping me understand this.
Is there a difference in the timing when each will be resolved?
No difference. The Promise executor (the callback you pass to new Promise()) is called immediately and synchronously. So, in BOTH cases, you are immediately and synchronously returning an already resolved promise and all the code in your function has already executed.
There should be no meaningful difference in execution timing. Of course, one might take slightly different number of CPU cycles to execute, but both have the exact same outcome and both immediately return a promise that is already resolved so there should be no difference to the calling code at all.
Promise.resolve() is just meant as a more compact (and probably more efficient) means of creating an already resolved promise.
Does Option 1 release control after all the work is done, and Option 2 release control as soon as the function is called?
They both return when all your work is done and they return an already resolved promise. This is because the executor callback to new Promise() is called synchronously (before your function returns).
I feel like this has root in the fact I don't fully understand the event loop. Thanks in advance for helping me understand this.
In this particular circumstance, all your code is run synchronously so there is no particular involvement of the event loop. The returnPromise() function returns (in both cases) when your code in the function is done executing synchronously. There is no async code here.
The event loop gets involved when you do .then() on the returned promise because even though the promise (in both cases) is already resolved, the .then() handler gets queued in the event queue and does not run until the rest of your Javascript after your .then() handler is done executing.
console.log("1");
returnPromise().then(function() {
console.log("2");
});
console.log("3");
This will generate identical output results:
1
3
2
with both versions of your returnPromise() function because .then() handlers are queued until the next tick of the event loop (e.g. after the rest of your current Javascript thread of execution is done).
Is there a difference with doing it this way?
No, both your examples do the exact same thing.
Promise.resolve and Promise.reject are simply static methods on the Promise Class that help you avoid constructing a whole Promise when you don't really need to.
For the below code
function inner () {
new Promise(function(resolve,reject){
resolve()
}).then(function(){
console.log('Inner Promise')
})
}
function outer() {
return new Promise(function(resolve, reject){
resolve()
inner()
})
}
outer().then(function(data) {
console.log('Outer Promise')
})
The output is
Inner Promise
Outer Promise
I thought the outer resolve would be the first to enter the JS Message Queue followed by the inner resolve.
However the JS Event Loop fires the Inner resolve first then followed by Outer resolve.
What does Promise resolve do internally?
In a nutshell, you get the behavior you see because the .then() method on the inner() promise runs first before the .then() method on the outer() promise and thus it's handler gets queued first (see step by step explanation below for why this is).
What does Promise resolve do internally?
resolve() changes the internal state of the promise to Fulfilled. At that moment, if there are any .then() handlers already attached to the promise, they are added to a queue to be executed when the stack unwinds and the current running path of Javascript finishes and returns control back to the system. Note, as you will see in this case (when you read the step-by-step analysis below), if there are not yet any .then() handlers that have been registered, nothing can yet be added to the queue.
I thought the outer resolve would be the first to enter the JS Message
Queue followed by the inner resolve. However the JS Event Loop fires
the Inner resolve first then followed by Outer resolve.
Promise resolve actions are not added to the queue. resolve() is synchronous. It changes the state of the current promise to the Fulfilled state immediately. If, at the time the promise is resolved, there are any .then() handlers already register, then they are what is added to a queue. But, in both your promises, at the moment each of your promises are resolved, there are no .then() handlers yet attached. So, those .then() handlers won't be queued at the point the promise is resolved. Instead, they will be queued later when the .then() method actually runs and registers them.
Here's a bit of an analysis of how your code runs and a likely explanation:
First you call outer(). This creates a Promise object and synchronously calls the promise executor callback you pass it.
That callback calls resolve() which will queue up the calling of any currently attached .then() handlers. Note, that at the moment you call resolve(), there are no .then() handlers yet because in this code outer().then(), you're still running outer() and the .then() after it has not yet run so there isn't actually yet anything to queue up yet (this is probably key to the ordering you observe - read on for further details).
Then, the code calls inner(). That creates a new promise and then (still running synchronously) calls the promise executor callback you pass there which calls resolve(). Again, there are not yet any .then() handlers attached so there is still yet nothing else to schedule for future execution.
Now, the Promise executor inside of inner() returns and the .then() method is called on that promise inside of inner(). This promise has already been resolved so, when this .then() handler is called, the promise knows to schedule it to run in the future. Since all .then() handlers are called asynchronously when the stack has unwound to only platform code, it is not run immediately, but it is scheduled to run in the future by puttiing it in a queue. It is implementation dependent exactly how this queue works (macro task or micro task, etc...), but we know it is guaranteed by the Promise specification to run after the current synchronous piece of JS code that is executing finishes running and returns control back to the system.
Now inner() returns (code is still running synchronously).
Now outer() returns and the .then() method in outer().then() runs. Just like in the previous example, when this .then() method is called, the host promise is already resolved. So, the promise engine will schedule the .then() handler callback to be run by adding it to the queue.
If these two .then() handlers in steps 4 and 6 are queued in the order they were run (which would be the logical implementation), then you would see the .then() handler on inner() run first and then the .then() handler on outer() would run since inner().then() ran first beforeouter().then()`. That is what you observe.
Even though outer() is resolved before inner() is, at the time outer() is resolved, there are not .then() handlers attached so there is nothing to schedule for future execution when it is resolved. This is likely why even though it is resolved first, its .then() handlers don't run first. Once both inner() and outer() are resolved, it is inner's .then() method that runs first, so it gets first crack at scheduling a .then() handler to run and this is what you observe.
You can get some additional context for what's going on by reading and studying these references:
What is the order of execution in javascript promises
Difference between microtask and macrotask within an event loop context.
If you wanted to more explicitly specify that the inner .then() handler would fire first, you can simply chain it to the outer() promise like this:
function inner () {
return new Promise(function(resolve,reject){
resolve();
}).then(function(){
console.log('Inner Promise')
})
}
function outer() {
// Add return here to chain the inner promise
// make to make sure that outer() does not resolve until
// inner() is completely done
return inner();
}
outer().then(function(data) {
console.log('Outer Promise')
})
If you wanted to guarantee that the outer().then() handler was called first, you'd have to pick a different structure since this structure does not force that type of order in any way and can't be cajoled that direction unless you consciously delay the running of inner() (using a setTimeout() or some such thing) or restructure the code. For example, if you really wanted to restructure to force inner() to run last, you would kick it off in the outer().then() handler like this:
function inner () {
return new Promise(function(resolve,reject){
resolve()
}).then(function(){
console.log('Inner Promise')
})
}
function outer() {
return new Promise(function(resolve, reject){
resolve()
})
}
outer().then(function(data) {
console.log('Outer Promise')
return inner();
})
I thought the outer resolve would be the first to enter the JS Message Queue followed by the inner resolve.
Yes, the "outer" promise is resolved first. Put a console.log right next to the resolve call.
But no, the outer then callback is not put in the queue first because it installed after the inner then callback. What you are doing is essentially equivalent to
var outer = Promise.resolve();
var inner = Promise.resolve();
inner.then(function() {
console.log('Inner Promise')
});
outer.then(function(data) {
console.log('Outer Promise')
});
but obfuscated due to the nested (synchronous) function calls.
Let's say I have a Promise like this:
var promise = new Promise(function(resolve, reject) {
// Do some async thing
});
promise.then(function(response) {
// Then do some other stuff
});
What happens if the async Promise completes before I call .then()? Normally, I'd only have long running tasks in the Promise function, but what if it completes really quickly one time?
As expected: then callback will get called immediately in this case if then was called after promise has already resolved.
It's easy to test:
var promise = new Promise(function(resolve, reject) {
resolve(123);
});
setTimeout(function() {
promise.then(function(response) {
alert(response);
});
}, 1000)
As others have already pointed out, you can add callbacks with .then before or after the promise has been resolved, and you can even add more than one callback.
These callbacks will be called in the order they were added, but always asynchronously, after the current turn of the event loop. So if the promise has already been resolved when you add a .then, your handler will be called immediately, but in the "ascynchronous sense".
The Promises/A+ spec says:
[...] onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.
A promise has state, which means that even after the promise gets fulfilled, you can attach callbacks using .then to it, and they will be called, with the same result as if the promise was fulfilled after they were attached.
Fulfilled is the final state of a successful promise. This means that you can attach more handlers to the fulfilled promise in the future, using the promise as a cache for the original response.
.then() on MDN
then()
Calls one of the provided functions as soon as this promise is either
fulfilled or rejected. A new promise is returned, whose state evolves
depending on this promise and the provided callback functions.
The appropriate callback is always invoked after this method returns,
even if this promise is already fulfilled or rejected. You can also
call the then method multiple times on the same promise, and the
callbacks will be invoked in the same order as they were registered.
The then callback will never get called before the promise is resolved, which is what I think you mean by complete. However, if a promise is resolved before it is returned from a function, any additional success callbacks chained after that moment will still be executed. For example,
function getMeAResolvedPromise() {
var prom = new Promise();
prom.resolve('some val');
return prom;
}
...
getMeAResolvedPromise.then(function(result) {
// this will still be executed
});