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:

  1. setTimeout schedules the callback.
  2. console.log("bar") runs.
  3. 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:

  1. Running through the body of an asynchronous function
  2. 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.

Leave a Comment