I am new to Javascript and trying to understand the execution engine of JS. I know that any async code statement moves to the call-stack and then gets immediately removed and executed in a separate Web API thread (started by browser). And then the result is stored in a callback queue and those values are picked up by the event loop once the call stack is empty.
Can anyone confirm the order in which below mentioned statements will be moved to call stack ?
Promise.resolve(function1)
.then(function2)
.then(function3)
.then(function4);
console.log("Hello");
Does the whole promise chain move to call stack or individual thens ?
Does the whole promise chain move to the call stack or individual thens ?
Not the whole chain. Also be careful how you express things here. The individual thens are executed synchronously. It is the callback to a then that is executed asynchronously. I suppose you refer to those callbacks.
The chained then methods are executed on different promise objects, and each will queue a reaction handler in the Promise Job Queue,
but only at the time the corresponding promise resolves.
So we have at least 4 promise objects that are created synchronously here. The call to Promise.resolve creates the first one,
which is immediately resolved. All three then methods are executed as well, creating 3 pending promises.
Lets call these 4 promise objects A, B, C and D. So A is resolved, the others are pending.
As promise A is resolved, an entry is put on the Promise Job Queue. Let's call that H(A) ("Handler for reacting to resolved promise A")
This all happens synchronously. After the synchronous script finally executes the console.log, the call stack is empty and the Promise Job Queue is processed:
The Promise Job Queue has H(A). It is pulled from that queue and put on the call stack.
This triggers the first then callback, i.e. function2.
Its return value is used to resolve promise B.
We should consider here the case where function2 returns a promise E (could be a thenable), but let's assume first that it is just returning a non-thenable.
Promise B is resolved with that value, and a new entry H(B) is put on the Promise Job Queue.
The callstack is empty again.
The Promise Job Queue is processed again, which now has H(B), ... and so it continues.
This will all happen in one single task if function2, function3 and function4 return non-thenables, but the Job Queue will not have H(A), H(B), H(C), H(D) at the same time.
The queue will in this scenario only have one of them at a time.
More realistic is when a function like function2 returns the result of a call to an asynchronous API, such as fetch.
In that case, promise B will be made dependent on the promise E that function2 returns. This dependency includes an asynchronous call to the then method of E.
Without going into too much detail about that process, the essence is that the Promise Job Queue may not get an entry H(E) immediately.
This will only happen when promise E resolves. And when it does, the call stack will get H(E) from the Job Queue.
Its execution will involve a call to H(B), because of the dependency.
Because of this delay, the current Task will find at some point that the Job Queue is empty, and the task ends.
It will be then for the Event Loop to detect when there is something ( like H(E) ) on the Job Queue.
Some other remarks
executed in a separate Web API thread
There is no separate Web API thread for executing such asynchronous code. All this happens in one thread.
That being said, there can be APIs involved that have non-JS parts, for instance accessing OS functions, which may execute via other (non-JS) threads. Also there is the concept of
Web Workers which get their own execution thread. But that has nothing to do with Promises.
the result is stored in a callback queue
It is not the result of asynchronous code execution that is stored in a callback queue. The Promise API stores a Promise Reaction Record in the queue at the time the promise is resolved. In case of Promise.resolve(), this actually happens as part of the synchronous script execution.
those values are picked up by the event loop once the call stack is empty.
There is a precision to make here: there are different queues. The Promise Job Queue has priority over other queues, and so for instance user I/O events will not be processed as long as there are entries on the Promise Job Queue. The execution of these entries are considered part of the same Task.
Related
So based on 2 StackOverflow answers, what I have understood is:
XHR callback is queued with Macrotasks
Fetch method is queued with Microtasks
So my question is:
Is this true?
If yes, why is it this way? Shouldn't both of them be treated in the same way?
Is this true?
No. Re-read the answer your linked:
When the the request response will be received […], the browser will queue a new task which will only be responsible of resolving that Promise, […]
I've emphasised the macrotask for you.
Shouldn't both of them be treated in the same way?
No, why would they? One is a promise API, the other is not. Notice that if you wrap XMLHttpRequest in a promise, you get exactly the same behaviour: the load/readystatechange event (a macro task) resolves a promise, scheduling any promise handler (a micro task).
But ultimately you should ask yourself: does it even matter? You normally shouldn't need to concern yourself with such timing details.
Is this true?
Yes.
When XMLHttpRequest was created there was no microtask queue. Only one - what is now called the macrotask queue.
However, when fetch() was introduced, promises were already in the standard. The result of fetch() is a promise and all effects after a promise resolution are done via the microtask queue:
setTimeout(() => console.log("macrotask done"), 0); //logged second
Promise.resolve().then(() => console.log("microtask done")); //logged first
Hence resolving the promise from fetch() will also add the subsequent handlers to the microtask queue. Again, it is the one used for the handlers all promises.
If yes, why is it this way? Shouldn't both of them be treated in the same way?
There is no requirement for the two to work the same. Nor would the resolution of these make much of a practical difference in day-to-day code.
Do note that the two are not really the same, either - fetch will resolve as soon as a result is returned before the body of the result is read. Hence why calling .json() or .text() is needed, see Why does .json() return a promise? - calling those methods that will actually process the body. XHR does not have this intermediate step required, its body is processed once it assumes ready state 4 (done).
Let's suppose that a fetch request is executed at 2ms. The program keeps running. Then, at 100ms the request has been completed, but the program hasn't finished its execution.
1. At this point, does the browser actually update the value in the Promise object even though the program hasn't finished yet, or does this have to be done in Javascript's thread, when the call stack is empty?
2. How do the onFulfilled and onRejected callbacks get enqueued to the Job queue?
As far as I know, they do so when the Promise's state changes, but how exactly is this done?
Is the browser behind the scenes "watching" for changes in the object and enqueuing the callback immediately, or is it Javascript that does it when finishing executing synchronous code?
EDIT: I was told in a course that, roughly speaking, what happened under the hood was that the Promise object's property "value" was updated as soon as the request was successfully completed (even if the program was still running), and immediately triggered two things: a change on the Promise's state, and the callback in onFulfillment array to be enqueued in the microtask queue (assuming one was added using then). Is this possible? Does the Promise object actually get modified outside Javascript, or is this done when the program finishes its execution?
Please correct me if I make any mistakes.
Assuming that the fetch call will succeed without error, at 100ms when the Request has been correctly sent and the Body is being streamed, a new fetch task is enqueued in the Networking task source which will process the response. This final algorithm is the one responsible to resolve the Promise returned by window.fetch().
So if at 100ms, the browser has something else to do, it will continue to do whatever it was. If when it's done doing that, it feels it has a more important task to do (e.g UI event?), then it will do that before. When it will proceed that fetch task, it will resolve the Promise, and doing so, it will queue a microtask to execute your callback, right after that task is done processing (i.e synchronously here).
const fetchPromise = fetch('example.com') // Takes 1 second to resolve
...
... // Do 3 seconds of work
...
const response = await fetchPromise // What happens with the promise between seconds 2-3?
What happens with the promise response before you resolve the promise?
In the example above, what happens with the fetch response in those 2 seconds where your program has items in its call stack, but you don't settle the promise? Does Node have some kind of cache for completed but unresolved promises?
here there is a great reading on the event loop.
In a high level perspective there is a forever running loop listening for events in the form of list of actions and processes them one at a time.
The code you shared above cannot be used globally due to the single threaded design of nodejs. What nodejs does is that every time you call a function it is pushed into the queue and processed. In every tick of the you pop something off the queue and run it or push something new.
In js world no 2 things can run at the same time. If you run a long running js code it blocks the entire system thus your system does not respond to new events since the eventloop is blocked.
That said there is another side of the story when you run all those nonblocking functions they are handled behind the scenes in v8 js engine. Since v8 native code it can make use of things like multiple threads and wait for things to finish or poll things checking whether they are ready and ones things are ready a new message gets inserted back into the js event loop and you get access to the data in js world.
So coming back to your question in an async function when you call await it really just means you yield the execution to the other things waiting in the message queue and once the thing you awaited gets ready it is pushed back to the queue and you continue from where you left of.
There is not really a waiting in the and the point of await is not to block the event loop with a blocking native function and to yield the execution to other things and you async function in the end of the day returns a Promise which only makes sense if you use .then and add a callback to be passed back to js world when the data is ready.
The spec says (para 5):
The PendingJob records from a single Job Queue are always initiated in
FIFO order. This specification does not define the order in which
multiple Job Queues are serviced. An ECMAScript implementation may
interweave the FIFO evaluation of the PendingJob records of a Job
Queue with the evaluation of the PendingJob records of one or more
other Job Queues.
Does this mean I can't count on the callback supplied to .then being evaluated before a callback supplied to setTimeout in an otherwise synchronous control flow?
In other words, can I depend on the following printing one two.
setTimeout(() => console.log('two'));
Promise.resolve().then(() => console.log('one'));
Does this mean I can't count on the callback supplied to .then being evaluated before a callback supplied to setTimeout in an otherwise synchronous control flow?
Yes, that's what it means; the spec doesn't require that implementations work that way.
But in practice, the implementations with native Promise support I've tested it on have scheduled the then callback (a "microtask" from the PendingJobs queue) immediately after finishing the "macrotask" that scheduled it, before other pending macrotasks, even when the pending macrotask was scheduled before the microtask. (setTimeout and events are macrotasks.)
E.g., in the environments where I've tested it, this outputs A, C, B reliably:
console.log("A");
setTimeout(_ => console.log("B"), 0);
Promise.resolve().then(_ => console.log("C"));
But the JavaScript spec doesn't require it.
As Bergi points out, for user agent environments, the HTML5 spec covers this in its specification for microtasks and macrotasks. But that's only applicable to user agent environments (like browsers).
Node doesn't follow that spec's definition, for instance (not least because its timer functions return objects, not numbers), but Node also gives us A, C, B above, because (thanks Benjamin Gruenbaum!) it runs promise resolutions after the nextTick queue but before any timer or I/O callbacks. See his gist for details.
Yes, that's what it means - an other event might fire before the promise callback.
No, that won't happen - while ECMAScript allows it, the setTimeout spec does not.
setTimeout does not mean that the supplied function will be executed after the provided time. It adds the function to the end of the queue once the delay has elapsed.
It really depends on when your promise resolves, as to the execution of the two statements. In your example, setTimeout will add it's callback to the queue ahead of the resolved promise, so you can expect one two.
Considering the following JavaScript code:
var promise = new Promise();
setTimeout(function() {
promise.resolve();
}, 10);
function foo() { }
promise.then(foo);
In the promise implementations I've seen, promise.resolve() would simply set some property to indicate the promise was resolved and foo() would be called later during an event loop, yet it seems like the promise.resolve() would have enough information to immediately call any deferred functions such as foo().
The event loop method seems like it would add complexity and reduce performance, so why is it used?
While most of my use of promises is with JavaScript, part of the reason for my question is in implementing promises in very performance intensive cases like C++ games, in which case I'm wondering if I could utilize some of the benefits of promises without the overhead of an event loop.
All promise implementations, at least good ones do that.
This is because mixing synchronicity into an asynchronous API is releasing Zalgo.
The fact promises do not resolve immediately sometimes and defer sometimes means that the API is consistent. Otherwise, you get undefined behavior in the order of execution.
function getFromCache(){
return Promise.resolve(cachedValue || getFromWebAndCache());
}
getFromCache().then(function(x){
alert("World");
});
alert("Hello");
The fact promise libraries defer, means that the order of execution of the above block is guaranteed. In broken promise implementations like jQuery, the order changes depending on whether or not the item is fetched from the cache or not. This is dangerous.
Having nondeterministic execution order is very risky and is a common source of bugs. The Promises/A+ specification is throwing you into the pit of success here.
Whether or not promise.resolve() will synchronously or asynchronously execute its continuations really depends on the implementation.
Furthermore, the "Event Loop" is not the only mechanism to provide a different "execution context". There may be other means, for example threads or thread pools, or think of GCD (Grand Central Dispatch, dispatch lib), which provides dispatch queues.
The Promises/A+ Spec clearly requires that the continuation (the onFulfilled respectively the onRejected handler) will be asynchronously executed with respect to the "execution context" where the then method is invoked.
onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
Under the Notes you can read what that actually means:
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.
Here, each event will get executed on a different "execution context", even though this is the same event loop, and the same "thread".
Since the Promises/A+ specification is written for the Javascript environment, a more general specification would simply require that the continuation will be asynchronously executed with respect to the caller invoking the then method.
There are good reasons to this in that way!
Example (pseudo code):
promise = async_task();
printf("a");
promise.then((int result){
printf("b");
});
printf("c");
Assuming, the handler (continuation) will execute on the same thread as the call-site, the order of execution should be that the console shows this:
acb
Especially, when a promise is already resolved, some implementations tend to invoke the continuation "immediately" (that is synchronously) on the same execution context. This would clearly violate the rule stated above.
The reason for the rule to invoke the continuation always asynchronously is that a call-site needs to have a guarantee about the relative order of execution of handlers and code following the then including the continuation statement in any scenario. That is, no matter whether a promise is already resolved or not, the order of execution of the statements must be the same. Otherwise, more complex asynchronous systems may not work reliable.
Another bad design choice for implementations in other languages which have multiple simultaneous execution contexts - say a multi-threaded environment (irrelevant in JavaScript, since there is only one thread of execution), is that the continuation will be invoked synchronously with respect to the resolve function. This is even problematic when the asynchronous task will finish in a later event loop cycle and thus the continuation will be indeed executed asynchronously with respect to the call-site.
However, when the resolve function will be invoked by the asynchronous task when it is finished, this task may execute on a private execution context (say the "worker thread"). This "worker thread" usually will be a dedicated and possibly special configured execution context - which then calls resolve. If that resolve function will synchronously execute the continuation, the continuation will run on the private execution context of the task - which is generally not desired.
Promises are all about cooperative multitasking.
Pretty much the only method to achieve that is to use message based scheduling.
Timers (usually with 0 delay) are simply used to post the task/message into message queue - yield-to-next-task-in-the-queue paradigm. So the whole formation consisting of small event handlers works and more frequently you yield - more smoothly all this work.