Dealing with async actions in JavaScript

Before promises become a thing in JavaScript we had to rely on callback functions if we needed something to be done after an asynchronous action. Let's say we have two functions and we want them to run in sequence, but the first function has some async action happening inside it such as fetching data from a rest api. This is how we did it in the past:

function firstFunction(callback) { 
    // setTimeout() is used to simulate async action   
    setTimeout(() => {
        callback('hello world');
    }, 500);
}
    
function secondFunction(greeting) {
    console.log(greeting);
}
    
firstFunction(secondFunction);

With promises we can do the same without using callback functions:

function firstFunction() {    
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('hello wrold');
        }, 500);
    });
}
    
function secondFunction(greeting) {
    console.log(greeting);
}
    
firstFunction().then(secondFunction);

In other words, promises allow us to chain functions using their then() method which is a cleaner syntax than using callback functions, specially when more async functions need to run after one another so you can chain them like this:

// using callbacks 
firstFunction(
    secondFunction(
        thirdFunction(
            fourthFunction();
        );
    );
);

// using promises
firstFunction()
.then(secondFunction)
.then(thirdFunction)
.then(fourthFunction);

With await keyword available in JavaScript now you can run async functions like normal functions without chaining. The only catch is that await keyword can only be used inside an async function so we need a async function wrapper around our code like this:

async () => {
  await firstFunction();
  await secondFunction();
  await thirdFunction();
  console.log('all async stuff are done');
}

If you have several async functions which do not depend on one another, a more performant approach would be to execute them in parallel using Promise.all():

async () => {
  await Promise.all([
    firstFunction(), 
    secondFunction(), 
    thirdFunction()
  ]);
  console.log('all async stuff are done');
}

Here is an example of a function that returns data asynchronously:

const data = ['apple', 'banana', 'kiwi', 'orange'];
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
       resolve(data);
    }, 100);
  });
};
fetchData().then(data => {
 console.log(data);
});

The keyword async allows us to simplify fetchData():

const data = ['apple', 'banana', 'kiwi', 'orange'];
const fetchData = async () => {
  return data;
};
fetchData().then(data => {
 console.log(data);
});

A function that is prefixed with async keyword always returns a promise and if we return a value in such function, then JavaScript behind the scenes invokes the resolve method of the async function with that value.