Is it true that the resolve() function and then() function below are the only functions in the ES6 Promise mechanism that look at and add the fulfillment handlers to the job queue (and mark them as already added and never again)?
let promise = new Promise(function(resolve, reject) {
console.log("resolve, reject are", resolve, reject);
setTimeout(function() {
resolve("this is the success data");
}, 1000);
});
promise.then(
function(a) {
console.log("Success.", a);
promise.then(function(a) { console.log("Go have a drink.", a); });
},
function(a) {
console.log("failure", a)
}
);
An alternative to the above code is, the resolve() is done directly, without being in the setTimeout(). Is it true that the then(fn) will add the fn to the fulfillment handler table (not job queue) for this promise, or if the promise has already been resolved, directly add the fn to the job queue, and the fn is marked as "already added to job queue" and no need to be added again?
On the other hand, the resolve() is a JS ES6 provided function, that mark a promise as "resolved" (state), and check if the fulfillment handler table for this promise is empty or not. If there are such handlers (and should not have been marked already added to the job queue before), then these handlers are added to the job queue and marked as already added.
And in any event, suppose there are fn1, fn2, fn3, ... etc in the fulfillment handler table, each one is added to the job queue as fn1(resolvedValue), where the resolvedValue is remembered internally by the resolve(resolveValue) call?
And there are no other situation where the fulfillment handler table is accessed at all. The job queue jobs are executed by the JS event loop, so that when all the GUI "click event handlers", etc are done, then the fulfillment (and rejection) handlers in the job queue are executed, and then also the setTimeout and setInterval handlers are also executed? Is this how the fulfillment handler table and job queue work?
Is it true that the resolve() function and then() function below are the only functions in the ES6 Promise mechanism that look at and add the fulfillment handlers to the job queue?
Yes. (reject also looks at the table and removes handlers without adding them to the job queue).
Is it true that the then(fn) will add the fn to the fulfillment handler table (not job queue) for this promise, or if the promise has already been resolved, directly add the fn to the job queue?
Yes.
and the fn is marked as "already added to job queue" and no need to be added again?
No, the fn is not marked at all. It could be passed multiple times to then, in which case it would need to be run multiple times.
But yes, the fulfillment handler table is cleared when the promise fulfills and the callback jobs are scheduled.
And in any event, suppose there are fn1, fn2, fn3, ... etc in the fulfillment handler table, each one is added to the job queue as fn1(resolvedValue), where the resolvedValue is remembered internally by the resolve(resolveValue) call?
Yes. Although technically resolve does not always fulfill the promise.
And there are no other situation where the fulfillment handler table is accessed at all.
Yes. I wonder why you care though.
The job queue jobs are executed by the JS event loop, so that when all the GUI "click event handlers", etc are done, then the fulfillment (and rejection) handlers in the job queue are executed, and then also the setTimeout and setInterval handlers are also executed? Is this how the fulfillment handler table and job queue work?
Yes. However notice that click handlers, setTimeout and setInterval have a separate event queue from promise jobs.
Related
I was learning promises in JS and got curious on how promises work with Job queues behind the scenes. To explain my confusion I want to show you this code:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
})
If you look at the above code, is it true that the callback of then() is put into Job queue beforehand and waits for promise to resolve? Or Is it true that callback of then() is pushed into job queue only after promise gets resolved?
When it's time to call a promise callback, the job doesn't go on the standard job queue (ScriptJobs) at all; it goes on the PromiseJobs queue. The PromiseJobs queue is processed until it's empty when each job from the ScriptJobs queue ends. (More in the spec: Jobs and Job Queues.)
I'm not sure what output you were expecting from your code as you didn't say, but let's take a simpler example:
console.log("top");
new Promise(resolve => {
setTimeout(() => {
console.log("timer callback");
}, 0);
resolve();
})
.then(() => {
console.log("then callback 1");
})
.then(() => {
console.log("then callback 2");
});
console.log("bottom");
The output of that, reliably, is:
top
bottom
then callback 1
then callback 2
timer callback
because:
The ScriptJobs job to run that script runs
console.log("top") runs
The promise executor function code runs, which
Schedules a timer job for "right now," which will go on the ScriptJobs queue either immediately or very nearly immediately
Fulfills the promise (which means the promise is resolved before then is called on it) by calling resolve with no argument (which is effectively like calling it with undefined, which not being thenable triggers fulfillment of the promise).
The first then hooks up the first fulfillment handler, queuing a PromiseJobs job because the promise is already fulfilled
The second then hooks up the second fulfillment handler (doesn't queue a job, waits for the promise from the first then)
console.log("bottom") runs
The current ScriptJob job ends
The engine processes the PromiseJobs job that's waiting (the first fulfillment handler)
That outputs "then callback 1" and fulfills the first then's promise (by returning)
That queues another job on the PromiseJobs queue for the callback to the second fulfillment handler
Since the PromiseJobs queue isn't empty, the next PromiseJob is picked up and run
The second fulfillment handler outputs "then callback 2"
PromsieJobs is empty, so the engine picks up the next ScriptJob
That ScriptJob processes the timer callback and outputs "timer callback"
In the HTML spec they use slightly different terminology: "task" (or "macrotask") for ScriptJobs jobs and "microtask" for PromiseJobs jobs (and other similar jobs).
The key point is: All PromiseJobs queued during a ScriptJob are processed when that ScriptJob completes, and that includes any PromiseJobs they queue; only once PromiseJobs is empty is the next ScriptJob run.
I would say callback of then() is pushed into job queue only after promise gets resolved.
If you changed the first timeout to 3000, you run the code and it waits until 3's to alert 1. This is because you have to wait the promise to be resolved in 3 seconds.
You get get the answer from here: https://stackoverflow.com/a/30910084/12733140
promiseA.then()'s callback is a task
promiseA is resolved/rejected: the task will be pushed into microtask queue in current round of event loop.
promiseA is pending: the task will be pushed into microtask queue in the future round of event loop(may be next round)
So here microtask is the same as "job" as you mentioned above, only the promise is resolved or rejected, the callback will be pushed to job/microtask queue.
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);
});
So I been reading a tutorial about Javascript promises these days.
here is an example from it used to explain the macrotask queue(i.e. the event loop) and the microtask queue.
let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));
// no error, all quiet
window.addEventListener('unhandledrejection', event => alert(event.reason));
It says that because promise.catch catches the error so the last line, the event handler never gets to run. I can understand this. But then he tweaked this example a little bit.
let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')));
// Error: Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));
This time he says the event handler is gonna run first and catch the error and after this the promise.catch catches the error eventually.
What I do not understand about the second example is, why did the event handler run before the promise.catch?
My understanding is,
Line one, we first encounter a promise, and we put it on the microtask queue.
Line two, we have a setTimeout, we put it on the macrotask queue,
Line three, we have an event handler, and we put the handler on the macrotask queue waiting to be fired
Then, because microtask has higher priority than macrotask. We run the promise first. After it, we dequeue the first task on macrotask queue, which is the setTimeout. So from my understanding, the error should be caught by the function inside setTimeout.
Please correct me.
You are wrong about step 3). The handler will be added synchronously. Then the microtask queue gets run, and the promise rejects. As no .catch handler was added yet, an unhandled rejection gets thrown.
And I think you are mixing between when a callback gets added and when a callback gets executed. Consider this case:
(new Promise).then(function callback() { });
The callback will be added synchronously, but it will never be called as the promise never resolves. In this case:
Promise.resolve().then(function callback() { });
the callback again gets added synchronously, but the promise resolution happens in a microtask, so the callback will be executed a tick later.
Consider the following code:
function foo() {
console.log('foo');
new Promise(
function(resolve, reject) {
setTimeout(function() {
resolve('RESOLVING');
}, 5000);
}
)
.then(
function(value) {
console.log(value);
}
);
}
foo();
I am trying to understand what happens here correctly:
on executing new Promise the "executer function" is run directly and when setTimeout is called, an operation to add a new entry to the "event queue" is scheduled (for 5 seconds later)
because of the call to then an operation to add to a "job queue" a call to the passed function (which logs to the console) is organised to happen after the Promise is resolved
when the setTimeout callback is executed (on some tick of the event loop), the Promise is resolved and based on point 2, the function argument to the then call is added to a "job queue" and subsequently executed.
Notice I say [a "job queue"] because there is something I am not sure about; which "job queue is it?". The way I understand it, a "job queue" is linked to an entry on the "event queue". So would that be the setTimeout entry in above example?
Assuming no other events are added to the "event queue" before (and after) setTimeout's callback is added, wouldn't the entry for the main code (the call to foo) have been (usually) gone (run to completion) by that time and thus there would be no other entry than setTimeout's for the then's "job queue" entry to be linked to?
on executing new Promise the "executer function" is run directly and when setTimeout is called, an operation to add a new entry to the "event queue" is scheduled (for 5 seconds later)
Yes. More specifically, calling setTimeout schedules a timer with the browser's timer mechanism; roughly five seconds later, the timer mechanism adds a job to the main job queue that will call your callback.
because of the call to then an operation to add to a "job queue" a call to the passed function (which logs to the console) is organised to happen after the Promise is resolved
Right. then (with a single argument) adds a fulfillment handler to the promise (and creates another promise that it returns). When the promise resolves, a job to call the handler is added to the job queue (but it's a different job queue).
when the setTimeout callback is executed (on some tick of the event loop), the Promise is resolved and based on point 2, the function argument to the then call is added to a "job queue" and subsequently executed.
Yes, but it's not the same job queue. :-)
The main job queue is where things like event handlers and timer callbacks and such go. The primary event loop picks up a job from the queue, runs it to completion, and then picks up the next job, etc., idling if there are no jobs to run.
Once a job has been run to completion, another loop gets run, which is responsible for running any pending promise jobs that were scheduled during that main job.
In the JavaScript spec, the main job queue is called ScriptJobs, and the promise callback job queue is PromiseJobs. At the end of a ScriptJob, all PromiseJobs that have been queued are executed, before the next ScriptJob. (In the HTML spec, their names are "task" [or "macrotask"] and "microtask".)
And yes, this does mean that if Job A and Job B are both queued, and then Job A gets picked up and schedules a promise callback, that promise callback is run before Job B is run, even though Job B was queued (in the main queue) first.
Notice I say [a "job queue"] because there is something I am not sure about; which "job queue is it?"
Hopefully I covered that above. Basically:
Initial script execution, event handlers, timers, and requestAnimationFrame callbacks are queued to the ScriptJobs queue (the main one); they're "macrotasks" (or simply "tasks").
Promise callbacks are queued to the PromiseJobs queue, which is processed until empty at the end of a task. Thatis, promise callbacks are "microtasks."
The way I understand it, a "job queue" is linked to an entry on the "event queue".
Those are just different names for the same thing. The JavaScript spec uses the terms "job" and "job queue." The HTML spec uses "tasks" and "task queue" and "event loop". The event loop is what picks up jobs from the ScriptJobs queue.
So would that be the setTimeout entry in above example?
When the timer fires, the job is scheduled in the ScriptJobs queue.
Assuming no other events are added to the "event queue" before (and after) setTimeout's callback is added, wouldn't the entry for the main code (the call to foo) have been (usually) gone (run to completion) by that time and thus there would be no other entry than setTimeout's for the then's "job queue" entry to be linked to?
Basically yes. Let's run it down:
The browser loads the script and adds a job to ScriptJobs to run the script's top-level code.
The event loop picks up that job and runs it:
That code defines foo and calls it.
Within foo, you do the console.log and then create a promise.
The promise executor schedules a timer callback: that adds a timer to the browser's timer list. It doesn't queue a job yet.
then adds a fulfillment handler to the promise.
That job ends.
Roughly five seconds later, the browser adds a job to ScriptJobs to call your timer callback.
The event loop picks up that job and runs it:
The callback resolves the promise, which adds a promise fulfillment handler call to the PromiseJobs queue.
That job ends, but with entries in PromiseJobs, so the JavaScript engine loops through those in order:
It picks up the fulfillment handler callback job and runs it; that fulfillment handler does console.log
That job is completely done now.
More to explore:
Jobs and Job Queues in the JavaScript specification
Event Loops in the HTML5 specification
The actual output (when seen in Firefox console) of the below code is
- error1
- error2
- continue1
- continue3.
Promise
.resolve()
.then(() => Promise.reject('error1'))
.catch(console.log)
.then(() => console.log('continue1'))
Promise
.resolve()
.then(() => Promise.reject('error2'))
.then(() => console.log('continue2'), console.log)
.then(() => console.log('continue3'))
As per my understanding of promises, it should be - error1 - continue1 - error2 - continue3.
Need help in understanding the actual output
First off, it is kind of pointless to attempt to understand a possible ordering issue between two completely independent promise chains. When programming, you should treat their order as indeterminate (and in fact, as soon as you have real asynchronous operations inside the promise chains which any real promise chain implementations would usually contain, the order will become indeterminate). If the order matters to you between the two chains, then you need to add some specific code to coordinate the actions of the two independent promise chains.
So, the observed order in your snippet is this:
error1
error2
continue1
continue3
Since you have no async operations in any of your .then() or .catch() handlers, it will simply alternate between the two promise chains (which is what it appears to be doing when you run your snippet). Here's how it would basically execute:
First Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Second Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Your sequence of JS is done so control returns back to the event queue. First .then() handler is called which then inserts another event into the event queue to run the .catch() handler and returns control back to the event loop.
Second .then() handler is at the top of the event queue and gets to run
And so on...
Keep in mind that per the promise specification each successive .then() or .catch() handler must be run asynchronously (on the next turn of the event queue). The promise chain doesn't execute as far as it can in one fell swoop. It executes the next step, puts the step after that into the queue and then returns control to the event loop which pops the next event off the queue. This will cause control to rotate between different promise chains that aren't waiting for something else to complete.
But, I repeat, you should not be depending upon this level of execution order and, in more complicated real-world cases with other async operations involved, you could not event attempt to predict an order. You should assume that these two separate promise chains run in an indeterminate order beyond the synchronous part (each Promise.resolve()). And, then if any specific ordering matters to your code, you must add code to actually synchronize and control the execution order using Promise.all() or linking the chains together into one branched chain or one sequence chain or something like that depending upon the needs of the code.
Promises that resolve/reject put a microtask in the corresponding queue. That task involves calling any then/catch callback functions. The messages in that queue are processed in sequence.
So in your example the sequence is:
First Promise.resolve() is executed. The microtask queue now has 1 entry:
Queue = [promise #1 resolved]
Normal execution flow continues to the second Promise.resolve()
Second Promise.resolve() is executed. The microtask queue now has 2 entries:
Queue = [promise #1 resolved, promise #2 resolved]
Normal execution flow ends -- the call stack is empty.
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error1') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise #2 resolved, promise.then #1 rejected]
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error2') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise.then #1 rejected, promise.then #2 rejected]
The first item in the queue is extracted and processed. It is a rejection, so the catch callback console.log is executed. This outputs "error1" and creates a new, resolved promise (with undefined value). The queue
Queue = [promise.then #2 rejected, promise.then.catch #1 resolved]
... etc
I am not completely sure the exact execution order of unrelated promises is specified anywhere, and even if it is, you probably want to introduce some explicit ordering dependency between the two promises if this is important to you.
But here is one possible and likely sequence:
Step one: Both promises are already resolved
Step two: Being resolved, both promises run their first then. This results in both being rejected. I would not depend on the order in which these two execute with respect to each-other.
Step three: Being rejected, both promises run their second catch/then. I would not depend on the order in which these two execute with respect to each-other.
You can probably depend on these steps going in that order (because it follows the queuing order of how these callbacks were submitted to the scheduler), but even that seems risky.
If you want to have the continue1 happen before the error2 you have to arrange code that waits for completion of the former.