What are asynchronous functions in JavaScript? What is “async” and “await” in JavaScript?

Intro

JavaScript has an asynchronous model. Whenever an asynchronous action is completed you often want to execute some code afterwards. First callbacks were
often used to solve this problem. However, when programming code with multiple asynchronous elements a problem arises using callbacks. Because when you nest multiple callbacks inside each other the code becomes hard to maintain very fast. This anti-pattern is called callback hell.

Promises

Promises solved many of the issues that appeared in nested callbacks. A key property of promises are that they can be nicely chained together using a promise chain. This allows for much cleaner syntax than callbacks and easier error handling. Here is an example:

const randomProm = new Promise((resolve, reject) => {
   if (Math.random() > 0.5) {
     resolve('Succes');
   } else {
     reject('Failure');
   }
  
});

// Promise chain
randomProm
  .then((value) => {
    console.log('inside then1');
    console.log(value);
    return value
}).then((value) => {
    console.log('inside then2');
    console.log(value);
    return value
}).catch((value) => {
    console.log('inside catch');
    console.log(value);
});

Asynchronous functions

Asynchronous functions are built on top of promises. They allow a more convenient use of Promises. Asynchronous functions have the following properties:

  • async before a function declaration/expression turns the function into an async function. As the name suggests async function are executed asynchronously.
  • An async function always returns a promise. It wraps any returned value in Promise.resolve(returnval). However, when an uncaught error is thrown inside the async function it wraps the return value in Promise.catch(returnval).
  • Inside async functions you can use the await keyword which can be used before any promise. await makes JS code execution stop until the promise is settled. i.e. The promise has to be either fulfilled or rejected until any further code inside the async function is executed.
  • await either returns the value of the fulfilled promise, or throws an error in the case of a rejected promise. We can use regular try-catch to catch the error.

Let’s clarify this with some examples:

Example1:

const randomProm = new Promise((resolve, reject) => {
    if (Math.random() > 0.5) {
        resolve("Succes");
    } else {
        reject("Failure");
    }
});

// async keyword creates an async function which returns a promise 
async function ansyncExample() {

    try {
        const outcome = await randomProm;
        console.log(outcome);
    } catch (error) {
        console.log(error);
    }

    // This return value is wrapped in a promise
    return 'AsyncReturnVal';
}

// ansyncExample() returns a promise, we can call its corresponding then method
ansyncExample().then((value) => {
    console.log(value);
});

console.log('I get executed before the async code because this is synchronous');

Example2:

// We can use async in function expressions
const randomProm = async () => {
    if (Math.random() > 0.5) {
        // This value is wrapped in Promise.resolve()
        return "Succes";
    } else {
        // This value is wrapped in Promise.reject()
        throw "Failure";
    }
};

// async keyword creates an async function which returns a promise
async function ansyncExample() {
    // randomProm is async fuction which returns a promise which we can await
    return await randomProm();
}

// ansyncExample() returns a promise, we can call its corresponding then/catch method
ansyncExample()
    .then((value) => {
        console.log("Inside then");
        console.log(value);
    })
    .catch((value) => {
        console.log("Inside catch");
        console.log(value);
    });

console.log("I get executed before the async code because this is synchronous");

When to use asynchronous functions

In theory you could use async function everytime when you are using a promise. However the power of async functions really starts to shine when there are multiple async operations that return promises and which depend on each other.

Because async function allow us to write asynchronous promise based code in a synchronous manner. The code is still asynchronous but we can now read it in a synchronous manner. It is more easily read and maintained than promise chains and thus scales better (IMO).

Here is an example of this nice readability:

async function ansyncExample() {
    try {
        // 3 async operations which depend on each other
        const firstValue = await firstAsyncFunc();
        const SecondValue = await secondAsyncFunc(firstValue);
        const ThirdValue = await thirdAsyncFunc(SecondValue);
    } catch (error) {
        console.log(error);
    }

    return ThirdValue;
}

Leave a Comment