What is Asynchronous Javascript?

Author:

Asynchronous JavaScript is best described as being able to multitask while running one program and working on another. In other words, if your program is running a particularly long task, asynchronous coding allows you to work on other tasks while that code is running. 

Code Institute graduate Guillermo Brachetta explains Asynchronous JavaScript with some intriguing examples in this article. 

Hugo’s Office

It’s 9 in the morning, and Hugo’s day has just started.

Like any other day, the first thing Hugo does is get some coffee, so he approaches the machine, makes a choice (double espresso) and waits. Someone says ‘good morning’, but he doesn’t hear them straight away. He only realizes once the coffee is ready (it takes a minute or two) and replays their words back. Hugo grabs his cup and goes to his desk to start his computer. Hugo stares at the screen: he is strangely fascinated by that black rectangle coming to life. He particularly enjoys the progress bar reaching the end.

There’s a message from his boss: Hugo needs to print a long document and get it signed by the client by the end of the day. So he presses the button and waits for the documents to be all out of the printer.

Hugo writes an email to their client asking what would be a suitable time to sign the papers. He sends it and waits. Finally, after two hours, a reply comes in, and he eagerly reads the email: the signature can take place by the end of the day, so he sits back, relaxes, and waits.

Fabio’s Office

Fabio works at Hugo’s competitor company, and they have a similar routine. Fabio also likes coffee and goes every morning to the machine. While he waits for his coffee to be ready, he catches up with colleagues. Back to his desk, he turns his computer on, and while it starts, he gives his boss a call regarding the contract they need to finalize today. His boss gives him a couple of straightforward instructions and raises a point or two. His computer is finally on, and they can discuss details by looking at the email from the client. Fabio quickly adjusts the contract while on the phone. By the time the conversation is over, the document has already been sent to the printer to be signed.

Fabio then emails the client asking for a good moment to get the contract signed, and immediately after starts to fill in the gaps from another document that needs attention. There’s a long day ahead, and he’s counting on closing a few important deals, but it all looks good, and it’s only 9:30.

Synchronous Code

As we’ve seen at Hugo’s office, every action happens in succession, and Hugo needs to wait for each of them to be completed before he can do anything else. Or even worse: Hugo could be sending an empty envelope to his client if he doesn’t let the contract be out of the printer first. This is synchronous code, and the problem is apparent: there’s code running that is blocking the execution of the rest of it until the resources are free, or comes in an undesired order, or creates an error.

JavaScript is very fast, but some actions require time, no matter how little. Think, for example, of a request to a database that may take some time or even a complex mathematical calculation; synchronous code execution will at least block the rest of the code or break it.

This is an example of synchronous code

<pre><code class="lang-js">const showGreeting = <span class="hljs-function"><span class="hljs-params">(content)</span> =></span> {
    <span class="hljs-built_in">console</span>.log(content);
}

const runMeFirst = <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
    showGreeting(<span class="hljs-string">"Hello"</span>);
}

const runMeNext = <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
    showGreeting(<span class="hljs-string">"World!"</span>);
}

runMeFirst();
runMeNext();
</code></pre>

The code above will print in the console

Hello
World!

Because the functions are executed in the order specified, and they take very negligible time. But let’s now imagine that the first function takes some longer time, say it is a request to fetch some data from a database. We will simulate an asynchronous request that needs time to yield a result by adding a setTimeout function with a duration of 1 second (1000 milliseconds).

const showGreeting = (content) => {
  console.log(content);
};

const runMeFirst = () => {
  setTimeout(() => {
    showGreeting('Hello');
  }, 1000);
};

const runMeNext = () => {
  showGreeting('World!');
};

runMeFirst();
runMeNext();

When we execute them, the result, in this case, will be

World!
Hello

And that’s certainly not what we want. Instead, we want our mock asynchronous function to run in the correct order.

How can we fix this?

Asynchronous Execution

There are three ways to run code asynchronously:

  • Callback functions
  • Promises (ES6)
  • Async/Await (ES8)

Callback Functions

Callbacks are the original way JavaScript used to run code asynchronously. It basically is a function that is passed as a parameter to another function and is executed when the previous one has finished.

For our code above, this is an implementation of asynchronously running the code above:

const showGreeting = (content) => {
  console.log(content);
};

const runMeFirst = (callback) => {
  setTimeout(() => {
    showGreeting('Hello');
    callback();
  }, 1000);
};

const runMeNext = () => {
  showGreeting('World!');
};

runMeFirst(runMeNext);

In it, the runMeFirst function is passed a callback function as a parameter. This callback function, runMeNext, is executed when the first function has finished.

While this works perfectly well, it’s easy to realize that this is fine in the given example, but that it becomes a lot more difficult to read and maintain as soon as the code gets bigger, and we need to deal with more complex scenarios and multiple asynchronous functions. It is a very common pattern to see nested callbacks deepened into the call, with the last callback being the one that is executed when all the previous ones have finished. This can become what’s commonly called callback hell, when a succession of callbacks functions end up being nested inside each other, making the code awkwardly confusing.

Luckily, there came a solution to this problem: the Promise object.

Promises

Promises were first introduced with ES6 in 2015, and they are a way to handle asynchronous code in a way that is more readable and easier to use.

A promise is an object that represents the eventual completion or failure of an asynchronous operation or, in other words, it is an object that represents an operation that hasn’t been completed yet.

A promise is in one of the following states:

  • Pending: its initial state, neither fulfilled nor rejected.
  • Fulfilled: when the operation has completed successfully.
  • Rejected: when the operation failed.

A promise in a pending state is said to be unresolved, and we need to wait for it to be either resolved or rejected before we can do anything with it.

Let’s assume that the timeout function returns a promise, for example, let’s imagine that it’s a request to fetch some data from a remote server. We will mock the time it takes for the promise to resolve by using that setTimeout() that takes 1 second, and we will use the Promise constructor with our setTimeout() in order to simulate that fetch request that needs some time to complete.

const runMeFirst = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    showGreeting('Hello');

    // Error handling would be needed here.
    // For the sake of our example let's assume that the fetch request was successful.
    const error = false;

    if (!error) {
      // If there's no error we resolve the promise.
      resolve();
    } else {
      // If there's an error we reject it and handle the error.
      reject(new Error('Something went wrong'));
    }
  }, 1000);
});

Now let’s incorporate our new function into our code:

const showGreeting = (content) => {
  console.log(content);
};

const runMeFirst = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    showGreeting('Hello');
    const error = false;

    if (!error) {
      resolve();
    } else {
      reject(new Error('Something went wrong'));
    }
  }, 1000);
});

const runMeNext = () => {
  showGreeting('World!');
};

runMeFirst()
  .then(runMeNext)
  .catch((err) => console.log(err)

As we can see above, we deal with the promise using the .then and .catch methods, instructing our promise to run, and then execute the next function only after the first one has finished:

runMeFirst().then(runMeNext)

This handles the resolve scenario (that is, if the promise was successful). We then use the .catch method to handle the reject scenario (that is, if the promise failed).

Subsequent promises can be chained together, for example:

runMeFirst()
  .then(runMeNext)
  .then(oneMorePromise)
  .then(yetAnotherPromise)
  .catch((err) => console.log(err));

Another way to handle a series of chained promises is using the Promise.all method. This method takes an array of promises as a parameter and returns a new promise that is resolved when all the promises in the array have resolved, or it is rejected when any of the promises in the array have been rejected, and the error is passed to the catch method of the only .then() method of the returned promise:

Promise.all([runMeFirst, runMeNext, oneMorePromise, yetAnotherPromise]).then((values) => {
  console.log(values) // Handle resolved values here.
}).catch((err) => console.log(err)); // Handle rejected values here.

Async/Await

A newer way to handle asynchronous code was made available with the introduction of the async and await keywords in ES8 (or ES2017).

These new keywords make it even easier to read, write and understand asynchronous code, turning the series of chained .then() and .catch() calls into a very natural, human-readable format.

In order to deal with a promise in this way, we need to use the async keyword in the function and await for the result of the promise.

Let’s create a new function, which we’ll call init for this example, that uses the async keyword:

const init = async () => {
  await runMeFirst();
  runMeNext();
};

It reads very similar to the way we talk, and it’s immediately clear what we are expecting to happen:

  • We know that our function is going to deal with asynchronous code by the use of the async keyword.
  • We can immediately see that it is going to deal with a promise, so we need to use the human-readable await keyword (in other words, when we see await before a function we can be sure we are expecting a promise to be returned).
  • The following function is synchronous, and thus it doesn’t need to be preceded by any keyword.

Our previous example can then be rewritten as:

const showGreeting = (content) => {
  console.log(content);
};

// The function returns a promise.
const runMeFirst = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      showGreeting('Hello');
      const error = false;

      if (!error) {
        resolve();
      } else {
        reject(new Error('Something went wrong'));
      }
    }, 1000);
  });

// The synchronous function.
const runMeNext = () => {
  showGreeting('World!');
};

// The new asynchronous function using async/await.
const init = async () => {
  await runMeFirst();
  runMeNext();
};

// Call the new asynchronous function.
init();

So, even though JavaScript is a single-threaded language, we can still make it work as if it was capable of ‘multitasking’ thanks to its asynchronous capabilities, made very accessible by the introduction of promises with ES6 and easy to read by the incorporation of the async and await keywords with ES8.

Conclusion

Asynchronous JavaScript unleashes the full power the language has to offer, and the relatively recent introduction of progressive ways to use it makes us be sure of the health and future of JavaScript.

Modern frameworks such as React, Next.js and Vue make extensive use of the power of asynchronous code. They are tremendously popular and powerful, and the steady growth and refinement of these and new libraries and frameworks is a clear indication of the exciting potential JavaScript has yet to offer for a long time ahead.

Guillermo Brachetta, Code Institute Graduate

Learn some coding basics for free

If you want to learn some of the basics of JavaScript for free, try this free 5 Day Coding Challenge. On it, you will learn the basics of HTMLCSS and JavaScript. It takes just one hour a day over five days. Register now through the form below. Alternatively, if you want to learn full-stack software development, you can read more about our programme here.

What is an AI Developer & How to Become One?

Right now, the world is witnessing an artificial intelligence technological revolution, and AI Developers are at the forefront. AI developers are those who design, develop, and deploy AI-powered solutions. They are shaping the technological future by creating intelligent systems and applications. We look into the world of AI developers, exploring their roles, skills, and the […]

Systems Analyst: A Guide 

A system analyst looks after a company’s computer systems and network and ensures they meet its goals. They ensure that the infrastructure, computers and other systems work efficiently.  Who do you think takes care of all the systems in a company and plans everything out for a firm to meet its needs? Who do you […]

What Does a .NET Developer Do & How to Become One?

.NET developers are essential in the realm of software development. They are in charge of planning, creating, testing, and managing software applications built with the Microsoft .NET framework. As a result, they are in high demand and can command substantial wages in the tech business. In this blog, we will look at what a .NET developer […]