In this article, we will learn how to 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]