Please point out the misfacts in my learning regarding Asynchronous Javascript
Callbacks of an Asynchronous function are put in a message queue and executed via event loop.
No.
There are generally two kinds of asynchronous functions - ones that take some sort of callback to run when they are done and ones that produce a Promise as a result.
Callback-based functions
This is an example of a callback-based function:
setTimeout(() => console.log("foo"), 0);
console.log("bar");
/* output:
bar
foo
*/
setTimeout
will delay a callback to be executed later. Even with a timeout of zero milliseconds, it still schedules the callback until after the current execution is complete, so the order is:
-
setTimeout
schedules the callback. -
console.log("bar")
runs. - The callback with
console.log("foo")
runs.
There isn't a message queue, there is a queue of tasks to execute. As a brief overview, the event loop takes one task from the queueand executes it to completion, then takes the next one and executes it to completion, etc. A simple pseudo-code representation would be:
while(true) {
if (eventQueue.hasNext()) {
task = eventQueue.takeNext();
task();
}
}
See here for more information on the event loop.
With all that said, the event loop has more bearing on setTimeout
than many other callback-based functions. Other examples of callback based functions are jQuery's $.ajax()
or the Node.js fs
module for file system access (the non-promise API for it). They, and others, also take a callback that will be executed later but the bounding factor is not the event loop but whatever underlying operation the function makes. In the case of $.ajax()
when the callback would be called depends on network speed, latency, and the server which responds to the request. So, while the callback will be executed by the event loop, so is everything else. So, it's hardly special. Only setTimeout
has a more special interaction here, since the timings might be imprecise based on what tasks are available - if you schedule something to run in 10ms and some task takes 12ms to finish, the callback scheduled by setTimeout
will not run on time.
Promise-based functions
Here is an example:
async function fn(print) {
await "magic for now";
console.log(print);
}
fn("foo")
.then(() => console.log("bar"));
/* output:
foo
bar
*/
(I'm omitting a lot of details for now for illustration.)
Promises are technically an abstraction over callbacks tailored towards handling asynchronous operations. The .then()
method of a promise takes a callback and will execute it after the promise gets resolved, which also happens after the asynchronous operation finishes. Thus, we can sequence together execution in the right order:
async function fn(print) {
await "magic for now";
console.log(print);
}
fn("foo");
console.log("bar");
/* output:
bar
foo
*/
In a way, promises are sort of callbacks because they are here to replace them and do it in broadly the same fashion. You still pass callbacks to be executed when something succeeds or fails. But they aren't just callbacks.
At any rate, a callback given to a promise is still delayed:
Promise.resolve()
.then(() => console.log("foo"));
console.log("bar");
/* output:
bar
foo
*/
But not via the same event queue as the one setTimeout
uses. There are two queues:
- macrotasks queue -
setTimeout
places things in it, and all UI interactions are also added here. - microtasks queue - promises schedule their things here.
When the event loop runs, the microtasks queue (so, promises) has the highest priority, then comes the macrotask queue. This leads to:
setTimeout(() => console.log("foo")); //3 - macrotask queue
Promise.resolve()
.then(() => console.log("bar")); //2 - microtask queue
console.log("baz"); //1 - current execution
/* output:
baz
bar
foo
*/
At any rate, I don't think I'm comfortable saying that promise-based functions work via callbacks. Sort of yes but in a reductionist sense.
Asynchronous execution is non-blocking, done via event loop.
No.
What is "blocking"?
First of all, let's make this clear - blocking behaviour is when the environment is busy executing something. Usually no other code can run during that execution. Hence, further code is blocked from running.
Let's take this code as example:
setTimeout(taskForLater, 5000);
while (somethingIsNotFinished()) {
tryToFinish();
}
Here taskForLater
will be scheduled to run in 5 seconds. However, the while
loop will block the execution. Since no other code will run, taskForLater
might run in 10 seconds time, if that's how lot it takes for the loop to finish.
Running an asynchronous function doesn't always mean it runs in parallel with the current code. The environment executes one thing at time in most cases. There is no multi-threading by default in JavaScript, parallel execution is an opt-in behaviour, for example by using workers.
What is "asynchronous execution"?
This can mean a couple of things and it's not clear which one you reference:
- Running through the body of an asynchronous function
- Waiting until the underlying asynchronous operation is finished
In both cases the quoted statement is wrong but for different reasons.
The body of an async function
Async functions are syntactic sugar over promises. They use the same mechanism but just present the code differently. However, an async function is blocking. As a side note, so are promise executors (the callback given to a promise constructor). In both cases, the function will run and block until something causes it to pause. The easiest way to demonstrate it is with an async function - using await
will pause the execution of the function and schedule the rest of it to be finished later.
Whole body is blocking:
async function fn() {
console.log("foo");
console.log("bar");
}
fn();
console.log("baz");
/* output:
foo
bar
baz
*/
Pausing in the middle:
async function fn() {
console.log("foo");
await "you can await any value";
console.log("bar");
}
fn();
console.log("baz");
/* output:
foo
baz
bar
*/
As a point of clarification, any value can be awaited, not just promises. Awaiting a non-promise will still pause and resume the execution but since there is nothing to wait for, this will cause it to be among the first things on the microtask queue.
At any rate, executing the body of an async function might block. Depends on what the operation is.
The underlying asynchronous operation
When talking about "underlying operation", most of the times we mean that we hand off the control to something else, for example the browser or a library. In this case, the operation is not fulfilled by the current JavaScript environment we call something which will perform an operation and only notify the current JavaScript environment when it finishes. For example, in a browser calling fetch
will fire off a network request but it's the browser handling it, not our code. So it's non-blocking but not by outside the execution environment.
fetch("https://official-joke-api.appspot.com/random_joke")
.then(res => res.json())
.then(joke => console.log(joke.setup + "\n" + joke.punchline));
console.log("foo");
With that said, we cannot even generalise what a given asynchronous operation will do. It might actually block the execution, at least for a while, or it might be entirely carried out in a separate process to the current JavaScript environment.
Functions like
setTimeout
are asynchronous.
Yes.
Although, it's probably considering what is like setTimeout
. Is it setInterval
? Is it any callback-based asynchronous function? The problem is that the definition starts to become circular "asynchronous callback-based functions like setTimeout
are asynchronous".
Not every function that takes a callback is asynchronous. Those that are might be considered similar to setTimeout
.
Asynchronous functions are blocking, only their callbacks are non-blocking.
No.
As discussed above, asynchronous functions might block. Depends on what exactly they do. For example $.ajax
will initiate a network request using the browser, similar to fetch
. In the case of $.ajax
the function blocks while preparing the request but not after it's sent.
The second problem with the statement is that it's incorrect to call the callbacks non-blocking - executing them will certainly block the execution again. The callback is a normal JavaScript code that will be executed via the event loop when its time comes. While the task is running to completion, execution is still blocked.
If you mean that they are non-blocking in the sense that the callback will be put on the event queue, that's still not guaranteed. Consider the following illustrative code:
function myAsyncFunction(callback) {
const asyncResult = someAsynchronousNonBlockingOperation();
doStuff(result);
callback(result);
doMoreStuff(result);
}
Once someAsynchronousNonBlockingOperation()
produces a value executing callback
will not be scheduled for later but will be part of the synchronous sequence of code that processes that result. So, callback
will be executed later but it will not be a task by itself but part of an overall task that encompasses doStuff
and doMoreStuff
.
Callbacks of an Asynchronous function are put in a message queue and executed via event loop.
Just in a queue (no messages). By asynchronous function I suppose you mean Promises. Remember async
and await
are syntactic sugar for Promises. They are execute by the event loop.
Asynchronous execution is non-blocking, done via event loop.
Not really. Javascript is single threaded: whenever it starts executing your async code, it stays there as long as needed, unless you use Workers.
Functions like setTimeout are asynchronous.
Like Promise, setTimeout
and similars put the callback in a queue, which will be executed somewhere in the event loop (which may behave different for each kind of async functions).
Asynchronous functions are blocking, only their callbacks are non-blocking.
Right!