When you look at any of the hands of a ticking clock, you will find out that it moves sequentially. It points at 1 before it points at 2, and then it points at 3 and it continues. It does not point at 4 and then 1 and then 9, in a random manner. It follows a sequence.

Imagine that the hand is the JavaScript runtime and each number is a function. The clock’s hand, which represents the JavaScript runtime, determines what function is executed by pointing at the number. When it points at 1, function1 runs, and when it points at 2, function2 runs.

When it points at 3 and function3 says

You’ve asked me to run and I will, but I will run in the background. Go ahead and ask the functions after me to run while I run in the background. When I run to completion, I will notify you and send you the return value of my execution

then function3 is an asynchronous function.

When function3 is done running, it notifies the JavaScript runtime. When the runtime is done handling all synchronous operations, it handles the notification from function3 and does whatever should be done done with the value returned from function3.

Callback functions are usually attached to asynchronous functions so that when the asynchronous function returns with a value, the callback function would automatically get executed with the return value.

Examples of Asynchronous Functions

It would make more sense help to add practical use cases of asynchronous functions.

setTimeout

setTimeout is an example of an asynchronous function. When setTimeout is executed with a function f and a timeout n passed in, it captures f and holds it in the backgroud for n milliseconds. After n milliseconds, it notifies the JavaScript runtime that it has run to completion.

The JavaScript runtime receives this notification and when it is done executing all synchronous operations (like the console.log operations in the code snippet below), it runs the callback function attached to the setTimeout call, causing it to log “I am function f” to the console.

That is why “I am function f” appears after “3” on the console.

console.log(1);
console.log(2);
setTimeout(() => {
  console.log("I am function f");
}, 0);
console.log(3);

fetch

According to MDN docs, the fetch API provides an interface for fetching resources. It is used for making network request most of the time.

A network request is an I/O operation. I/O operations do not have a predictable completion time. They could be slowed down by

  • a poor network
  • low computer speed
  • the resource server taking long to respond

and because of this, the JavaScript engine executes fetch in the background. If fetch is executed on the main thread, delays in running it to completion will block other operations from executing until the fetch operation been completely executed, and that will cause poor user experience.

Rejected or Fulfilled

Rejected or Fulfilled

When fetch is executed by the JavaScript runtime, it immediately returns a promise. This is to show that the result of the execution is pending and a result is to be expected.

A promise is like an empty gift box given to you when someone makes a promise to you. The box has two empty partitions when the “promiser” has not delivered on their promise. One partition gets filled with the real gift item (fulfilled) and the other partition gets filled with a reason why the gift could not be delivered (rejected). Only one of these boxes can be filled. When either of the boxes is filled, the promise is said to have been settled.

Both partitions have a callback function attached to them that get executed when the promise is settled. The then method accepts the callback function as an argument for the fulfilled partition. The catch method accepts the callback function as an argument for the rejected partition.

const prom = fetch()

prom
.then(runOnFulfilled)
.catch(runOnRejected)

In summary, when a fetch request is executed, it immediately returns a promise object and the JavaScript runtime runs the request in the background. When completed, the promise is settled and the JavaScript runtime is notified. When the JavaScript runtime is done executing all synchronous operations (for an event loop), it executes one of the callback functions attached to the promise. The callback function to run depends on whether the promise was rejected or fulfilled.

Conclusion

An asynchronous function is one that completely runs at a later time and will not prevent the statements after it from running because it is not run on the main thread. It will notify the runtime on completion for the runtime to take action after executing all synchronous operations.

People illustrations by Storyset