Promise.then Job execution order - javascript

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.

Related

What was the motivation for introducing a separate microtask queue which the event loop prioritises over the task queue?

My understanding of how asynchronous tasks are scheduled in JS
Please do correct me if I'm wrong about anything:
The JS runtime engine agents are driven by an event loop, which collects any user and other events, enqueuing tasks to handle each callback.
The event loop runs continuously and has the following thought process:
Is the execution context stack (commonly referred to as the call stack) empty?
If it is, then insert any microtasks in the microtask queue (or job queue) into the call stack. Keep doing this until the microtask queue is empty.
If microtask queue is empty, then insert the oldest task from the task queue (or callback queue) into the call stack
So there are two key differences b/w how tasks and microtasks are handled:
Microtasks (e.g. promises use microtask queue to run their callbacks) are prioritised over tasks (e.g. callbacks from othe web APIs such as setTimeout)
Additionally, all microtasks are completed before any other event handling or rendering or any other task takes place. Thus, the application environment is basically the same between microtasks.
Promises were introduced in ES6 2015. I assume the microtask queue was also introduced in ES6.
My question
What was the motivation for introducing the microtask queue? Why not just keep using the task queue for promises as well?
Update #1 - I'm looking for a definite historical reason(s) for this change to the spec - i.e. what was the problem it was designed to solve, rather than an opinionated answer about the benefits of the microtask queue.
References:
In depth: Microtasks and the JavaScript runtime environment
HTML spec event loop processing model
Javascript-hard-parts-v2
loupe - Visualisation tool to understand JavaScript's call stack/event loop/callback queue interaction
Using microtasks in JavaScript with queueMicrotask()
Promises were introduced in ES6 2015. I assume the microtask queue was also introduced in ES6.
Actually the microtask task queue was not introduced by ECMAScript standards at all: the ES6 standard specified putting promise handling jobs for a settled promise in a queue named "PromiseJobs" under TriggerPromiseReactions, using the abstract process EnqueueJob to enter the job in a job queue implemented by the host environment, without prescribing how the host queue should be handled.
Prior to adoption by ECMAScript
Promise libraries were developed in user land. The bit of code that executed promise handlers, monitored if they threw or returned a value and had access to the resolve and reject functions of the next promise in a promise chain was called the "trampoline". While part of the Promise library, the trampoline was not considered part of user code and the claim of calling promise handlers with a clean stack excluded stack space occupied by the trampoline.
Settlement of a promises with a list of handlers to call for the settled status (fulfilled or rejected) required starting the trampoline to run promise jobs if it were not already running.
The means of starting trampoline execution with an empty stack was limited to existing Browser APIs including setTimeout, setImmediate and the Mutation Observer API. The Mutation Observer uses the microtask queue and may be the reason for its introduction (not sure of the exact browser history).
Of the event loop interfacing possibilities, setImmediate was never implemented by Mozilla at least, Mutation Observers were available in IE11 according to MDN, and setTimeout under some circumstances would be throttled so it would take at least some milliseconds to execute a callback even if the delay time were set to zero.
Developer Competition
To an outside observer promise library developers competed with each other to see who could come up with the fastest time to begin executing a promise handler after promise settlement.
This saw the introduction of setImmediate polyfills which picked the fastest strategy of starting a callback to the trampoline from the event loop depending on what was available in the browser. YuzuJS /
setImmediate on GitHub is a prime example of such a polyfill and its readme well worth reading.
History After adoption in ECMAScript 2015
Promises were included in ES6 without specifying the priority host implementations should give to promise jobs.
The author of the YuzuJS/setImmediate polyfill above also made a submission to the TC39 committee to specify that promise jobs should be given high priority in ECMAScript. The submission was ultimately rejected as an implementation issue not belonging to the language standard. Arguments supporting the submission are unavailable on TC39's tracking site given it doesn't reference rejected proposals.
Subsequently the HTML5 specification introduced rules for Promise implementation in browsers. The section on how to implement ECMAScipt's EnqueueJob abstract operation in host browsers specifies that they go in the microtask queue.
Answer
What was the motivation for introducing the microtask queue? Why not just keep using the task queue for promises as well?
The micro task queue was introduced to support Mutation Observer Events, as detailed by Jake Archibald at JSConf Asia 2018 ( 24:07)1 during his "In the loop" presentation.
Early developers of promise libraries found ways to enter jobs in the micro task queue and in doing so minimized the time between settling promises and running their promise reaction jobs. To some extent this created competition between developers but also facilitated continuing asynchronous program operation as soon as possible after handling completion of one step in a sequence of asynchronous operations.
By design fulfillment and rejection handlers can be added to promises that have already been settled. If such cases there is no need to wait for something to happen before proceeding to the next step of a promise chain. Using the microtask queue here means the next promise handler is executed asynchronously, with a clean stack, more or less immediately.
Ultimately the decision to specify the microtask queue was made by prominent developers and corporations based on their expert opinion. While that may be excellent choice, the absolute necessity of doing so is moot.
See also Using microtasks in JavaScript with queueMicrotask() on MDN.
1 Thanks to #Minh Nghĩa 's comment for the link to Jake Archibald's "In the Loop" (0:00) talk - ☆☆☆☆☆. Highlights include
An event loop executes one task from the task queue at a time, all tasks in the animation queue except for tasks added while executing the queue, and all tasks in the microtask queue until it's empty.
Dependence on tricky execution order of event handlers and promise callbacks can cause unit testing failures because events dispatched programatically execute event handlers synchronously, not via the event loop.
One advantage is fewer possible differences in observable behavior between implementations.
If these queues weren't categorized, then there would be undefined behavior when determining how to order a setTimeout(..., 0) callback vs. a promise.then(...) callback strictly according to the specification.
I would argue that the choice of categorizing these queues into microtasks and "macro" tasks decreases the kinds of bugs possible due to race conditions in asynchronicity.
This benefit appeals particularly to JavaScript library developers, whose goal is generally to produce highly optimized code while maintaining consistent observable behavior across engines.
I found this (https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) relatively old blog post which explains it really well, and it also gives some examples on old browser versions.
Mainly, this separation is use to
Improve browser performance
Comply with the ECMAScript standard of execution order
Separate HTML related tasks and 'job'/micro tasks related work.
I think this behavior is needed to support workers on the browser. A worker doesn't have access to the DOM, so they had to come up with a new mechanism for that as well.

How does setInterval() run independently of sequential execution?

I'm a beginner in software development. To my knowledge, JavaScript runs sequentially from left to right, top to bottom, only skipping lines and returning carriages when functions are called. If that's the case, how can a program remember to run a setInterval function set to execute every 2000ms when it's currently occupied with other calculations?
Behind the scenes, there is a queue of “tasks” that JavaScript should run (the event loop). Tasks are functions. At the end of a function, JavaScript checks the queue to see if another function should be called.
setInterval only pushes functions into this queue, to be run at specified time.
JavaScript only has 1 thread, so if a heavy computation takes up a lot of time, events in the queue will be postponed until that task has completed.
This is why you do not want to run a blocking function in JS.
Although each JavaScript agent has a single executing thread - the so-called "event loop" - the runtime is not limited in the number of threads of execution it may use to service this.
Callbacks passed to functions such as setInterval, setTimeout, or requestAnimationFrame, are scheduled to be run by "magic" (ie. hidden logic) within the JavaScript runtime environment.
This hidden logic schedules the insertion of callbacks on job queues, at appropriate times (eg. after an interval has elapsed). When a job associated with a callback reaches the front of the relevant job queue, and when the executing thread is available to service it, the job is removed from the queue, a stack frame (aka execution context) is instantiated for it, pushed onto the call stack, and execution begins.
The logic for asynchronous functions such as setTimeout, setInterval and requestAnimationFrame are defined in other specifications (eg. W3C/WHATWG), and implemented by host applications (eg. a web browser or a NodeJS instance). These functions are not defined by the ECMAScript specification.
Asynchronous promise behavior, on the other hand, is specified within the ECMAScript specification.

Differences between requestIdleCallback and setImmediate?

There are currently 2 different API's aimed at breaking tasks up into scheduled callback functions.
setImmediate (non-standard)
requestIdleCallback (experimental)
To my knowledge there are no JavaScript runtimes (browser or Node) which currently implement both features.
I would like to better understand how the two features compare. I realize that requestIdleCallback has a timeout option which can be used to allow the browser to decide when to call it, and passes a IdleDeadline object to the callback where setImmediate passed any supplied arguments, but other than that how do these API's differ?
For example, are the following two examples functionally identical?
setImmediate(function() {
// Do something.
});
requestIdleCallback(function() {
// Do something.
}, {timeout: 0});
What about in the case where multiple callbacks are registered but the first one runs longer than the idle time?
setTimeout will wait for a time to pass, then it will execute the callback.
requestIdleCallback will wait for the main thread to be idle before executing the callback. Meaning that it won't slow animations, user can still click buttons and type into inputs.
How to choose between setTimeout and requestIdleCallback? setTimeout is best used when the callback must execute (Example: Sending data to an analytics server or logging out the user). requestIdleCallback is best for when you don't want to hurt the user experience in the cost of executing the callback (Example: Rerendering the DOM).
Keep in mind that requestIdleCallback is still experimental.
requestIdleCallback won't execute when document.visibilityState === 'hidden', it will execute all callback at once when it become visible.
At least in electron's browser view.

Are Node.js asynchronous tasks handled synchronously?

Consider the following piece of code:
var some_expensive_task = function(i) {
setTimeout(function() {
var a = 1;
while (a < 100000000) {
Math.sqrt(a);
++a;
}
console.log('finished set' + i);
}, 0);
};
for (var i = 0; i < 70; ++i) {
console.log('start ' + i);
some_expensive_task(i);
console.log('end ' + i);
}
The intent of this program was to iterate over and start 70 cpu intensive asynchronous tasks using setTimeout.
As expected, the output of this program is:
start 1
end 1
...
start 69
end 69
finished set1
...
finished set69
Throughtout the entire execution, there were only two processes. One of which I assume was the idle event loop and the other was the worker process running at 100%.
Am I correct in understanding that while the event loop is executed synchronously, asynchronous tasks that are started, regardless of origin, are executed synchronously in the order that they were called?
UPDATE
I still don't feel as if I've conveyed my question clear enough. Using setTimeout was the only way I could think of to accurately schedule a bunch of async functions to run. I guess the real question is, if I start three async functions with setTimeout, still with a zero delay, is the second one I start guaranteed to run after the first one finishes and is the third one guaranteed to start after the second one finishes?
You use incorrect terminology in the question, Are Node.js asynchronous tasks handled synchronously does not make much sense. And the test case is not what you think it is. First making clear some terminologies:
Synchronous functions are guaranteed to run/complete in the order of their invocation.
Asynchronous functions can progress and complete out-of-order w.r.to their calling.
Blocking operations are those operations which once started must complete for the execution to progress further. CPU-intensive operations are blocking unless you run them on a separate worker. loops are blocking in nature. Once they start, they will complete all iterations before next function is executed.
setTimeout simply calls the passed function at least x ms after current time. Calling it means once x ms pass it queues the passed function on the event loop. It is just delayed execution.
So considering above facts, here is why your test is misleading:
First your test case (your CPU-intensive function) is not asynchronous, you wrapped it around a setTimeout which is asynchronous. The timeout of 0 simply implies that they will be executed in the same order they are called. All callback functions being synchronous.
Second, start and end in the log indicates point where your setTimeout is called. This would be in-order as expected. With timeout 0, the completion will be in-order too. Yes if you keep timeout equal for them they all will be executed in the same order as passed. Then again this is one of many possible cases where outcome is similar to synchronous (what if timeouts were in increasing order?). What you asked may be true for these cases but not always.
Third if you wanted to simulate asynchronicity (enough to see out-of-order behaviour on console), might as well loose the while loop and use random timeouts(Math.random()*1000). You will see they complete in arbitrary fashion. This would be asynchronous execution (of setTimeout not the callback).
Node.js is single threaded and runs in one process because JavaScript is single threaded. Thus you are correct.
Yes. Javascript, including NodeJS, is single threaded (with a few exceptions).
When you use setTimeout(fn, 0), it queues up the function to run after the current call stack has cleared. In your example, that means that the for loop will complete before the "expensive tasks" get run.
Your JS code runs in a single thread in Node.js. All other native Node.js API are written in C/C++ and are either asynchronous or run on a separate thread.
See this answer for the more detailed explanation.
Am I correct in understanding that while the event loop is executed synchronously, asynchronous tasks that are started, regardless of origin, are executed synchronously in the order that they were called?
Yes, you are correct.

Why do Promise libraries use event loops?

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.

Categories

Resources