Execute async functions in parallel in JavaScript

In this article, we will learn how to execute async functions in parallel in JavaScript.

Execute async functions in parallel in JavaScript

It is a common JavaScript interview question and the problem statement reads as,

Implement a function in JavaScript that takes a list of async functions as input and a callback function and executes the async tasks in parallel that is all at once and invokes the callback after every task is executed.

Example

Input:
executeParallel([asyncTask(3), asyncTask(1), asyncTask(2)], (result) => {
console.log(result);
});

Output:
// output in the order of execution
[2, 1, 3]

We can use the simple forEach loop on each task and execute them parallel. We just need to use a tracker to determine the no of task that has been finished so that when each task is executed, we can invoke the callback function.

Invoking the callback function at the end of the loop won’t work as the async tasks could be in different stacks and queues and may execute after some time.

function asyncParallel(tasks, callback) {
  // store the result
  const results = [];
  
  // track the task executed
  let tasksCompleted = 0;
  
  // run each task
  tasks.forEach(asyncTask => {
    
    // invoke the async task
    // it can be a promise as well
    // for a promise you can chain it with then
    asyncTask(value => {
      // store the output of the task
      results.push(value);
      
      // increment the tracker
      tasksCompleted++;
    
      // if all tasks are executed 
      // invoke the callback
      if (tasksCompleted >= tasks.length) {
        callback(results);
      }
    });
  });
};

To create an async task, we have created a function that accepts a callback and runs a setTimeout for a random time and invokes this callback inside the timeout.

function createAsyncTask() {
  const value = Math.floor(Math.random() * 10);
  return function(callback) {
    setTimeout(() => {
      callback(value);
    }, value * 1000);
  };
}

Using this we can create multiple tasks and then test the async parallel.

Input:
const taskList = [
  createAsyncTask(),
  createAsyncTask(),
  createAsyncTask(),
  createAsyncTask(),
  createAsyncTask(),
  createAsyncTask()
];

asyncParallel(taskList, result => {
  console.log('results', result);
});

Output:
"results" // [object Array] (6)
[1,6,7,7,9,9]

Execute promises in Parallel

In the above example, we have seen the async task created through setTimeout, the same can be extended to execute the promises in Parallel.

First thing we will have to do is update the createAsyncTask() test function, this function will now return a promises that will reject if the random value is less than 5, else it will resolve.

function createAsyncTask() {
  const value = Math.floor(Math.random() * 10);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value < 5) {
        reject(`Error ${value}`);
      } else {
        resolve(value * 1000);
      }
    }, value * 1000);
  });
}

After this, we will modify the asyncParallel() function, we will use the same logic, but now will store the outcome of resolved as well as rejected promises in different arrays, and handle them using the then, catch, and finally block.

function asyncParallel(tasks, callback) {
  // store the result
  const results = [];

  const errors = [];

  // track the task executed
  let tasksCompleted = 0;

  // run each task
  tasks.forEach((asyncTask) => {
    // invoke the async task
    // it can be a promise as well
    // for a promise you can chain it with then
    asyncTask
      .then((value) => {
        // store the output of the task
        results.push(value);
      })
      .catch((error) => {
        errors.push(error);
      })
      .finally(() => {
        // increment the tracker
        tasksCompleted++;

        // if all tasks are executed
        // invoke the callback
        if (tasksCompleted >= tasks.length) {
          callback(errors, results);
        }
      });
  });
};

This will invoke the callback when all the promises are settled.

asyncParallel(taskList, (error, result) => {
  console.log("errors", error);
  console.log("results", result);
});


//errors (4) ["Error 0", "Error 1", "Error 1", "Error 2"]
//results (2) [5000, 9000]