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"