Everything to know about Asynchronous Programming in JavaScript

JavaScript is synchronous by nature. The implication of the synchronous nature of JavaScript is the delay in execution as long-running tasks are run to completion before proceeding ones are executed.

· 5 min read
Laurine Aluoch

Laurine Aluoch

Introduction

JavaScript is synchronous by nature, which means operations are executed in the chronological order that they are called. The implication of the synchronous nature of JavaScript is the delay in execution as long-running tasks are run to completion before proceeding ones are executed. In this article, we will look into asynchronous programming, and why we need it in JavaScript. Let’s begin by establishing an understanding of what asynchronous programming is;

Say we have functions a, b, c, and d written and called as below.

function a() {
  console.log('a');
}

function b() {
  console.log('b');
}

function c() {
 console.log('c');
}

function d() {
  console.log('d');
}

In the above, the functions will execute in the order provided with the console output showing;

/*
a
b
c
d
*/

The output is reflective of how synchronous JavaScript executes. While our code runs as expected, the challenge arising from the provided implementation is the blocking nature of synchronous code. Synchronous code is linear and sequential. Therefore code blocks are executed in a linear format based on how they are called from top to bottom. Given that some code blocks are long-running tasks that take significantly longer time to execute, the proceeding blocks of code will not be executed until the time-consuming code block is executed to completion. This often results in unresponsive applications that in turn impact user experience. Asynchronous programming solves the challenge of synchronous code.

What is asynchronous programming?

To understand asynchronous programming, we will explore callbacks, asynchronous code, promises, and finally async/await. Let’s begin with definitions and sample code for each concept;

Callbacks

Callbacks are functions that are passed as arguments to other functions. A callback function gets executed at a later time in the receiving function. For instance;

function displayData(output) {
    console.log(output);
}

function calculatePerimeter(width, height, displayOutput) {
  let perimeter = (2*(width + height));
  displayOutput(perimeter);
}

calculatePerimeter(5, 6, displayData);

In the above code, calculatePerimeter takes the parameters width, height, and displayOutput. When calling the function, we pass the arguments 5, 6, and displayData, displayData is the callback function. It is executed after the perimeter has been calculated.

Asynchronous code refers to code that runs in parallel with others. The implication, therefore, is that code blocks execute without blocking each other, each at its runtime. Often, asynchronous code is used with callback functions. A perfect example is the setTimout() method.

function displayMessage() {
  return "Happy Birthday";
}

setTimeout(displayMessage, 2000);

console.log(1);

In the above code, setTimeout() is an asynchronous function that runs in parallel with console.log(1). Given the delay, 1 will be displayed before the message is displayed. In setTimeout, displayMessage is a callback function.

NOTE: Callback functions are not called with the (parenthesis) i.e displayMessage and not displayMessage()

Promises

Promises are introduced into asynchronous programming given the complexity of writing asynchronous programs. Promises allow a program to complete other tasks while waiting for a specific function to finish, and then let the program know when to execute something else. A promise promises a result regardless of the outcome; success or failure.

function hailCab() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const success = Math.random() < 0.5;
            if (success) {
                resolve("Successfully hailed a cab");
            } else {
                reject("Someone else got it... oops!");
            }
        }, 2000);
    });
}

console.log("Waiting for driver to respond...");

hailCab()
    .then((message) => {
        console.log(message);
    })
    .catch((error) => {
        console.error(error); 
    });

console.log("Final shopping experiences...");

In the above code, the function hailCab returns a promise which takes resolve and reject as its functional parameters. We then use .then() and .catch() to handle the Promise outcome; resolved or rejected respectively. When we call the function, as it executes, other functions continue to run as expected.

async/await

To tie it all together, we will now look into async/await. async/await is built on top of promises and makes the writing of promises easier. async makes a function return a promise while await makes the function pause execution and wait for the resolved promise before it continues.

const winContest = async () => {
const success = Math.random() < 0.5; if (success) { return "Won"; } else { throw new Error("Lost!"); } } const handleWinContest = async () => { try { const result = await winContest(); console.log(result); } catch (error) { console.error(error.message); } } handleWinContest();

In the above code, we have an async winContest function that determines whether one wins a contest or not using a Math.random() function. We then handle the result of simulating winContest in handleWinContest(). In handleWinContest(), we use await to wait for the promise returned by winContest().

Note: When using async/await, we do not need to use the Promise constructor, as it is redundant, and we can directly return the resolved value. or throw an error.

As we have demonstrated above, asynchronous programming ensures that JavaScript code, which is traditionally synchronous runs asynchronously. Asynchronous programming, therefore, sufficiently improves the speed and eliminates blocking, further resulting in improved user experience.

share

Laurine Aluoch

Software Engineer