Recursive Implementation for Asyn.series

We are going to look into a recursive solution for the series method. Note that recursive always comes with a performance dent with a deeper call stack of instruction execution in the hardware.

The idea here will start with a function1 from the tasks array and then pass a wrapped callback function on reading the result of function1, then invoking function2 and vice versa. If any error occurs in the process, the algorithom will exit by calling the final callback with error and results collection.

    fn((error, result) => {
      if (error) {
        callback(error, results);
        return;
      }
      results.push(result);
      //invoke a recursive call to next method 
    });

The full code is available in the below code block and it is ready to execute in your console for analysis or debug to see how this works in action.

const sampleTasks =  [ function (callback) { setTimeout(function () { console.log("1"); callback(null, "one"); }, 200); }, function (callback) { setTimeout(function () { console.log("2"); callback(null, "two"); }, 100); }, function (callback) { setTimeout(function () { console.log("3"); callback(null, "three"); }, 90); }, function (callback) { setTimeout(function () { console.log("4"); callback(Error("some error")); }, 10); }, function (callback) { setTimeout(function () { console.log("5"); callback(null, "five"); }, 1000); }, ];
 
function onSeriesCompleted(error, results) {
  console.log("Error stack", error);
  console.log("Results of successfully executed tasks", results);
}

function startTask(tasks, callback, counter = 0, results = []) {
  if (counter < tasks.length) {
    const fn = tasks[counter];
    const fn2 = tasks[counter + 1];
    fn((error, result) => {
      if (error) {
        callback(error, results);
        return;
      }
      results.push(result);
      startTask(tasks, callback, ++counter, results);
    });
  }
}

function series(tasks, finalCallback) {
  startTask(tasks, finalCallback);
}

series(sampleTasks, onSeriesCompleted);

Output

Polyfill for Async.series(tasks, callback)

The series method was quite a different one when we compared it to other sequential call stacks. The series method will take two arguments one is an array of functions and another one is the finalCallback which is optional to call with the first occurred error and obtained results until then.

 

async.series( 
  [fn-1(callback), fn-2(callback), ... fn-n(callback)], 
  finalCallback( error, results ){ ... }
);

Now will talk about the implementation. We can solve this problem by making Asyncify each task from the tasks array. This way, we can extract the result from each task, review for errors, and move to the next task. Let’s move to the code implementation.

Asyncify a task


function Asyncify(task) {
  return new Promise((resolve, reject) =&gt; {
    task((error, result) =&gt; (error ? reject(error) : resolve(result)));
  });
}

That’s all we have completed with the implementation, and we need to use this method to queue up the tasks in a sequential way of execution.

async function series(tasks, finalCallback) {
  const allResults = [];
  for (let i = 0; i &lt; tasks.length; i++) {
    try {
      allResults.push(await Asyncify(tasks[i]));
    } catch (error) {
      finalCallback(error, allResults);
      break;
    }
  }
  console.log(allResults);
  finalCallback(null, allResults);
}

This solution is based on Promise API, and you can execute the below code in the browser console for quick analysis. And we can also implement a recursive-based solution and will look into it in our next post.
The full spec you can read from https://caolan.github.io/async/v3/docs.html#series.

Now, add some sample tasks to test and see how this works.

const sampleTasks =  [ function (callback) { setTimeout(function () { console.log("1"); callback(null, "one"); }, 200); }, function (callback) { setTimeout(function () { console.log("2"); callback(null, "two"); }, 100); }, function (callback) { setTimeout(function () { console.log("3"); callback(null, "three"); }, 90); }, function (callback) { setTimeout(function () { console.log("4"); callback(Error("some error")); }, 10); }, function (callback) { setTimeout(function () { console.log("5"); callback(null, "five"); }, 1000); }, ];

function onSeriesCompleted(error, results) {
  console.log("Error stack", error);
  console.log("Results of successfully executed tasks", results);
}

function Asyncify(task) {
  return new Promise((resolve, reject) => {
    task((error, result) => (error ? reject(error) : resolve(result)));
  });
}

async function series(tasks = [], finalCallback) {
  const allResults = [];
  for (let i = 0; i < tasks.length; i++) {
    try {
      allResults.push(await Asyncify(tasks[i]));
    } catch (error) {
      finalCallback?.(error, allResults);
      return; //remove if you want continue even after error
    }
  }
  finalCallback?.(null, allResults);
}

series(sampleTasks, onSeriesCompleted);

Output