Deep dive into Asynchronous Javascript

In this article we'll be covering callbacks, promises and async/await as these are the ways to deal with asynchronous data.

To understand asynchronous, you'll first have to understand synchronous.

What is synchronous?

Synchronous is when something is done in sequence. In programming terms synchronous is when a bunch of code are executed one after the other.

For example

function A() {
  console.log("Task 1");
  console.log("Task 2");
  console.log("Task 3");
}
A();

In the example above the code will run synchronously.

Task 1
Task 2
Task 3

It prints Task 1, Task 2, and Task 3 one after the other. It will wait for every line to complete its execution.

What is asynchronous?

Asynchronous is nothing but just the opposite of synchronous. You keep doing things while others are getting completed. What do you think the code will print?

console.log("Task 1");
setTimeOut(() => {
  console.log("Task 2");
}, 1000);
console.log("Task 3");

well the above code will print

Task 1
Task 3
Task 2

Why does it print that way? It is because setTimeout function is not called immediately. It is called after 1 second. Till the time it waits the third console.log() gets printed.

Why Asynchronous Javascript?

When a JavaScript code is executed, the synchronous code blocks further execution until it completes what it's doing. Code which takes long to complete can make the UI/server unresponsive until the function has returned, which result in a terrible user-experience.

For instance: You want the latest comments of your blog from a server and render in your blog, and it is done synchronously, then a visitor who is in your blog site won't be able to do other things until those comments are loaded. Which indeed could cause a long delay before they could read the comments in your blog. To understand the above example, follow the code:

const comments = loadCommentsFromDatabaseSync();

displayBlogInWebPage();

In the above code, in order to display the blog on website, the website will first have to wait for loadCommentsFromDatabaseSync() to get complete. Once loadCommentsFromDatabaseSync() is completed then only it will display the blog in the web page.

By using async

const comments = loadCommentsFromDatabaseAsync();

displayBlogInWebPage();

In the above code displayBlogInWebPage() will not wait for the completion of loadCommentsFromDatabaseAsync().

We should use asynchronous programming when performing expensive and time-consuming operations.

Different ways of Asynchronous programming

In javascript, there are three ways to accomplish asynchronous programming, namely callback, promises and async/await. Let us get in detail with example.:

Callback

What is a callback?

A callback is a function which is executed after a subsequent function has finished executing.

As JavaScript functions are also a type of object and they can be passed as an argument while calling a function so much like any other objects like string, number etc. Example:

function addTwoNumbers(a, b, callback) {
  console.log("Addition of two numbers: ", a + b);
  callback();
}

function print() {
  console.log("This must be printed after addition to the console");
}

addTwoNumbers(2, 3, print);

The output of the above code:

Addition of two numbers: 5
This must be printed after addition to the console

In the above example, we have two functions:

addTwoNumbers(a,b,callback): This functions is called with there arguments a,b and callback, where a and b are numbers while the third argument callback is a function. The addTwoNumbers() prints the addition of the two numbers, and as soon as that completes its execution, the callback function is fired!

This type of function is also known as the callback function.

print(): As soon as addTwoNumbers() completes its execution and call the callback function this print() will get called and prints its output to the console.

Callbacks are mainly used for handling asynchronous operations like – making an API request to the server, fetching/writing some data from/into a file, registering event listeners etc. Callbacks are used for the mentioned operations. Depeneding on the result of the operation the callback function will be executed.

Promises

To understand the basic of promises please check-out Basic of Promises.

This will be the continuation of the above link.

basic syntax of promise in javascript.

let promise = new Promise(function (resolve, reject) {
  //resolve or reject is done here
});

As discussed in the basics of Promises section promises have three states, and the states are self-explanatory:
Pending: Pending is a state when the promise is neither resolved or rejected. It will continue to remain indefinitely pending unless it gets resolved or rejected.
Resolved: A promise is resolved when the resolve method is called from the promise. The resolved promise will be consumed in the .then() section.
Rejected: If the reject function was called from the promise, then the promise is rejected. If the promise is rejected, then it should be consumed in the .catch() section.

Consuming a promise

Please check the above link in order to see consuming promises.

Chaining of promises

In order to understand the concepts of Async/await, one has to understand the core usage of promise, which includes chaining of promises.

When a .then() method returns a promise, then the subsequent executions of the .then() are suspended until the current promise block is resolved.

let firstPromise = new Promise(function (resolve, reject) {
  setTimeout(resolve, 1000, "Pratap");
});

let secondPromise = new Promise(function (resolve, reject) {
  setTimeout(resolve, 2000, "Prasar");
});

let thirdromise = new Promise(function (resolve, reject) {
  setTimeout(reject, 3000, "Error");
});

firstPromise
  .then((x) => {
    console.log("First Promise after 1 sec: ", x);
    return secondPromise;
  })
  .then((x) => {
    console.log("Second Promise after 2 sec: ", x);
    return thirdromise;
  })
  .catch((e) => {
    console.log("Third Promise after 3 sec: ", e);
  });

The output of the above code:

First promise after 1 sec:  Pratap
Second promise after 2 sec: Prasar
Third promise after 3 sec: Error

Explanation of above code.

Let us understand step by step:

  • We have initialised 4 promises firstPromise, secondPromise, thirdPromise and fourthPromise. For the first instance, all four promises are racing toward resolve/reject.
  • After 1 second the firstPromise gets resolved as we are calling the resolve method in the promise initialization and this gets printed in the console First promise after 1 sec: Pratap. Then we return another promise secondPromise.
  • After 2 seconds the secondPromise also gets resolved and Second promise after 2 sec: Prasar gets printed to the console. We then return thirdPromise from the secondPromise.
  • But, after 3 seconds thethirdPromisegets rejected as we called reject in the thirdPromise initialization. Third promise after 3 sec: Error gets printed to the console.

Promise.all

Promise.all accepts an array of promises, and will attempt to fulfil all the promises. If any of the promises are rejected, then the promise will exit.

In order to fulfil multiple promises, promise .all was introduced. It is widely used in javascript frameworks where we want to get data from multiple APIs. So rather then calling the promises individually you'd better want to use Promise.all() method and handle the result based on the status of the promises.

An example of promise.all:

const arrayOfPromises = [new Promise(promise1), new Promise(promise2)];

function runAllThePromises() {
  Promise.all(arrayOfPromises).then(showSuccessMessage).catch(showErrorMessage);
}

arrayOfPromises();

Promise.race

promise.race()method returns a promise which fulfills or rejects as soon as one of the promises in an array gets fulfills or rejects.

We can understand promise.race() as the realtime race.

Suppose five people are participating in a race and any of them crosses the winning line then the race ends. Also, if any of them gets injured, then also the race gets end.

const firstPromise = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, "First Promise");
});

const secondPromise = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "Second Promise");
});

Promise.race([firstPromise, secondPromise]).then((value) => {
  console.log(value);
});

Output:

Second Promise

Explanation:

We passed firstPromise and secondPromise as an array in the argument of Promise.race(). As secondPromise gets resolved faster than firstPromise so Second Promise gets printed in the console.

Promise.allSettled

Promise.allSettled() takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise.

const firstPromise = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, "First Promise");
});

const secondPromise = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "Second Promise");
});

Promise.allSettled([firstPromise, secondPromise]).then((values) => {
  console.log(values);
});

Output:

[{ status: 'fulfilled', value: 'First Promise' },{ status: 'fulfilled', value: 'Second Promise }]

Explanation:

We passed firstPromise and secondPromise as an array in the argument of Promise.allSettled(). When all promises in the given iterable have settled (either fulfilled or rejected) then it returns an array of objects, each describing the outcome of one promise in the iterable, in the order of the promises passed, regardless of completion order.

The result objects have two properties: status and value. The status can be either "fulfilled" or "rejected". If the Promise is fulfilled, the value property will contain the fulfilled value. If the Promise is rejected, the value property will contain the rejection reason.

Async/Await

Async/Await is one of the most recent additions to the JavaScript language which is part of ES8.

Async/Await is syntactic sugar on top of promises which makes asynchronous code easier to write and to read. The async code is written just like the synchronous code. It is just a special syntax to work with promises in a more synchronous-type manner.

Basic function:

const getFruit = () => {
  return "Mango";
};

The above snippet just a basic javascript code which returns Mango.

async

Now, let us convert the above code to promise using async keyword.

const getFruit = async (name) => {
  const fruits = ["Apple", "Banana", "Mango"];

  return fruits[name];
};

Now, in the function above, just adding async keyword in front of a function, not the function will return a Promise.

getFruit("Apple").then((res) => {
  console.log(res);
}); // prints: Apple

await

The await keyword is used to wait for a Promise to get resolved. await can be used inside an Async function block only. It makes JavaScript wait until the promise returns a result. It only makes the async function block wait and not the whole program execution.

async function demonstrateAsync() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("Promise is ressolved!"), 1000);
  });

  // wait until the promise returns us a value
  let result = await promise;

  console.log(result);
}

demonstrateAsync();

The output of the above code:

Promise is ressolved!

await should be used within an async block only.
await can't be used in top-level code

Catching error in async/await

Handling errors in async/await is pretty straight forward. In order to handle an error, we just have to wrap inside a try..catch block.

async function thisThrowsError() {
  try {
    let response = await fetch("http://invalidUrl");
  } catch (err) {
    console.log("Error: ", err); // Invalid url
  }
}
thisThrowsError();

Any error in the scope of try block will be handled in the catch block.

Conclusion

In this blog, we have completed the different ways of asynchronous programming in Javascript and their usage.

Due to the addition of asynchronous programming in javascript, it makes it possible to express waiting for long-running actions without blocking the execution of code. In javascript, it is typically implemented using callback, promises and async/await.

Programming asynchronously in Javascript is made easier by using promises and async/await, which allow us to write an asynchronous program as if it were synchronous.

FURTHER READING

💌 If you’d like to receive more tutorials in your inbox, you can sign up for the newsletter here.

Discussions

Up next