Execute async functions in Series

Implement a function that takes a list of async functions as input and executes them in a series that is one at a time. The next task is executed only when the previous task is completed.

Example

Input:
[
  asyncTask(3),
  asyncTask(1),
  asyncTask(2)
]

Output:
3
1
2

One of the complex parts of working with JavaScript is handling the async code and the same is tested in interviews to make sure you are an experienced developer who can handle it.

We will see three different approaches to solve this problem.

Using async/await

async/await is the new keyword introduced in ES6 that helps us to handle the promises by avoiding the callback chaining.

For…of loop allows using await keyword performing the next iteration only when the previous one is finished.

To handle the rejection, we wrap the async/await in the try-catch block.

const asyncSeriesExecuter = async function(promises) {
  for (let promise of promises) {
    try{
      const result = await promise;
      console.log(result);
    }catch(e){
      console.log(e);
    }
  }
}
Input:
const asyncTask = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 100*i)
  });
}

const promises = [
  asyncTask(3),
  asyncTask(1),
  asyncTask(7),
  asyncTask(2),
  asyncTask(5),
];

asyncSeriesExecuter(promises);

Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"

With recursion

We can execute the async tasks in series by recursively calling the same function after the current task is executed.

const asyncSeriesExecuter = function(promises) {
  // get the top task
  let promise = promises.shift();
  
  //execute the task
  promise.then((data) => {
    //print the result
    console.log(data);
    
    //recursively call the same function
    if (promises.length > 0) {
      asyncSeriesExecuter(promises);
    }
  });
}
Input:
const asyncTask = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 100*i)
  });
}

const promises = [
  asyncTask(3),
  asyncTask(1),
  asyncTask(7),
  asyncTask(2),
  asyncTask(5),
];

asyncSeriesExecuter(promises);

Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"

Using Array.reduce

Rather than calling the function recursively, we can use the Array.reduce to do the execution in series.

const asyncSeriesExecuter = function(promises) {
  promises.reduce((acc, curr) => {
    return acc.then(() => {
      return curr.then(val => {console.log(val)});
    });
  }, Promise.resolve());
}
Input:
const asyncTask = function(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Completing ${i}`), 100*i)
  });
}

const promises = [
  asyncTask(3),
  asyncTask(1),
  asyncTask(7),
  asyncTask(2),
  asyncTask(5),
];

asyncSeriesExecuter(promises);

Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"

Unbounded async tasks

In case the async task is not wrapped in promises, you can wrap them explicitly to run them in series.

Unwrapped async tasks.

//simply returns as setTimeout
const asyncTask = function(i) {
  return function(callback){
    setTimeout(() => {
      callback(`Completing ${i}`)
    }, 100*i);
  };
}

const tasks = [
  asyncTask(3),
  asyncTask(1),
  asyncTask(7),
  asyncTask(2),
  asyncTask(5),
];

To run them in series we can create a new promise every time and resolve it at the end of execution of setTimeout.

const asyncSeriesExecuter = function(tasks) {
  tasks.reduce((acc, curr) => {
    //return the last executed task
    return acc.then(() => {
      //create a new promise
      return new Promise((resolve, reject) => { 
        //exec the async task
        curr(val => {
          console.log(val);
          //resolve after task is completed
          resolve();
        }); 
      });
    });
  }, Promise.resolve());
}
Input:
const asyncTask = function(i) {
  return function(callback){
    setTimeout(() => {
      callback(`Completing ${i}`)
    }, 100*i);
  };
}

const tasks = [
  asyncTask(3),
  asyncTask(1),
  asyncTask(7),
  asyncTask(2),
  asyncTask(5),
];

asyncSeriesExecuter(tasks);

Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"