Asynchronous serial iteration over an array collection Node.js design patterns:Asynchronous serial iteration over a collection using callbacks. This is an examp
Node.js design patterns:
Asynchronous serial iteration over a collection using callbacks.
This is an example of a very common Node.js design pattern.
Serial asynchronous iteration over collections (arrays) using callbacks.
If you want to learn more about common (and not-so-common) Node.js design patterns, check this out.
Book Node.js design patterns by Mario Casciaro and Luciano Mammino modified by L3n4r0x support for typescript. source
Features
- iterate without callback (auto detect callback is called or not)
- support asynchronous or synchronous callback function
- typescript generic type supported
Javascript Asynchronous serial iteration over an array collection
"use strict";
/**
 * A callback based function that allows to iterate over a collection
 * and perform asynchronous actions on every element. The processing is
 * done is series, so an element is started only when the previous one
 * has been completed. In case of error the whole processing is interrupted
 * early and the error is propagated to the finalCallback to let the caller
 * decide how to handle it.
 *
 *
 * @param collection - The generic array of elements to iterate over
 * @param iteratorCallback - The callback based function that will
 *        be used to process the current element.
 *        It receives the current element as first paramenter and a callback
 *        as second parameter. The callback needs to be invoked to indicate
 *        the end of the asynchronous processing. It can be called passing an
 *        error as first parameter to propagate an error)
 * @param finalCallback - A function that is called when all the
 *        items have been processed or when an error occurred. If an error
 *        occurred the function will be invoked with a single argument
 *        representing the error.
 */
const iterateSeries = (collection, iteratorCallback, finalCallback) => {
    const stoppingPoint = collection.length;
    function iterate(index) {
        // console.log('it', index);
        if (index === stoppingPoint) {
            // console.log('final');
            return finalCallback();
        }
        else {
            const current = collection[index];
            let called = false;
            const done = function (err) {
                called = true;
                if (err) {
                    return finalCallback(err);
                }
                return iterate(index + 1);
            };
            if (iteratorCallback instanceof Promise) {
                iteratorCallback(current, done).then(done);
            }
            else {
                iteratorCallback(current, done);
            }
            if (!called)
                done();
        }
    }
    iterate(0);
};Typescript Asynchronous serial iteration over an array collection
/**
 * A callback based function that allows to iterate over a collection
 * and perform asynchronous actions on every element. The processing is
 * done is series, so an element is started only when the previous one
 * has been completed. In case of error the whole processing is interrupted
 * early and the error is propagated to the finalCallback to let the caller
 * decide how to handle it.
 *
 *
 * @param collection - The generic array of elements to iterate over
 * @param iteratorCallback - The callback based function that will
 *        be used to process the current element.
 *        It receives the current element as first paramenter and a callback
 *        as second parameter. The callback needs to be invoked to indicate
 *        the end of the asynchronous processing. It can be called passing an
 *        error as first parameter to propagate an error)
 * @param finalCallback - A function that is called when all the
 *        items have been processed or when an error occurred. If an error
 *        occurred the function will be invoked with a single argument
 *        representing the error.
 */
const iterateSeries = <T extends any[]>(
  collection: T,
  iteratorCallback:
    | ((currentElement: T[number], done: (...args: any[]) => any) => any)
    | ((currentElement: T[number], done: (...args: any[]) => any) => Promise<any>),
  finalCallback: (err?: Error) => any | Promise<any>
) => {
  const stoppingPoint = collection.length;
  function iterate(index: number) {
    // console.log('it', index);
    if (index === stoppingPoint) {
      // console.log('final');
      return finalCallback();
    } else {
      const current = collection[index];
      let called = false;
      const done = function (err?: Error | undefined) {
        called = true;
        if (err) {
          return finalCallback(err);
        }
        return iterate(index + 1);
      };
      if (iteratorCallback instanceof Promise) {
        iteratorCallback(current, done).then(done);
      } else {
        iteratorCallback(current, done);
      }
      if (!called) done();
    }
  }
  iterate(0);
};Usage Asynchronous serial iteration over an array collection
const deployInfo = [
  {
    name: 'root',
    dest: path.join(__dirname, '.deploy_git'),
    url: 'https://github.com/dimaslanjaka/dimaslanjaka.github.io',
    ref: 'master'
  },
  {
    name: 'docs',
    dest: path.join(__dirname, '.deploy_git/docs'),
    url: 'https://github.com/dimaslanjaka/docs',
    ref: 'master'
  },
  {
    name: 'chimeraland',
    dest: path.join(__dirname, '.deploy_git/chimeraland'),
    url: 'https://github.com/dimaslanjaka/chimeraland',
    ref: 'gh-pages'
  },
  {
    name: 'page',
    dest: path.join(__dirname, '.deploy_git/page'),
    url: 'https://github.com/dimaslanjaka/page',
    ref: 'gh-pages'
  }
];
iterateSeries(
  deployInfo,
  function (info) {
    console.log(`writing ${info.dest}`);
  },
  function (err) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
  }
);Live Playground
const path_1 = require("path")
/**
 * A callback based function that allows to iterate over a collection
 * and perform asynchronous actions on every element. The processing is
 * done is series, so an element is started only when the previous one
 * has been completed. In case of error the whole processing is interrupted
 * early and the error is propagated to the finalCallback to let the caller
 * decide how to handle it.
 *
 *
 * @param collection - The generic array of elements to iterate over
 * @param iteratorCallback - The callback based function that will
 *        be used to process the current element.
 *        It receives the current element as first paramenter and a callback
 *        as second parameter. The callback needs to be invoked to indicate
 *        the end of the asynchronous processing. It can be called passing an
 *        error as first parameter to propagate an error)
 * @param finalCallback - A function that is called when all the
 *        items have been processed or when an error occurred. If an error
 *        occurred the function will be invoked with a single argument
 *        representing the error.
 */
const iterateSeries = (collection, iteratorCallback, finalCallback) => {
    const stoppingPoint = collection.length;
    function iterate(index) {
        // console.log('it', index);
        if (index === stoppingPoint) {
            // console.log('final');
            return finalCallback();
        }
        else {
            const current = collection[index];
            let called = false;
            const done = function (err) {
                called = true;
                if (err) {
                    return finalCallback(err);
                }
                return iterate(index + 1);
            };
            if (iteratorCallback instanceof Promise) {
                iteratorCallback(current, done).then(done);
            }
            else {
                iteratorCallback(current, done);
            }
            if (!called)
                done();
        }
    }
    iterate(0);
};
// usage samples
const deployInfo = [
    {
        name: 'root',
        dest: path_1.join(__dirname, '.deploy_git'),
        url: 'https://github.com/dimaslanjaka/dimaslanjaka.github.io',
        ref: 'master'
    },
    {
        name: 'docs',
        dest: path_1.join(__dirname, '.deploy_git/docs'),
        url: 'https://github.com/dimaslanjaka/docs',
        ref: 'master'
    },
    {
        name: 'chimeraland',
        dest: path_1.join(__dirname, '.deploy_git/chimeraland'),
        url: 'https://github.com/dimaslanjaka/chimeraland',
        ref: 'gh-pages'
    },
    {
        name: 'page',
        dest: path_1.join(__dirname, '.deploy_git/page'),
        url: 'https://github.com/dimaslanjaka/page',
        ref: 'gh-pages'
    }
];
iterateSeries(deployInfo, function (info) {
    console.log(`writing ${info.dest}`);
}, function (err) {
    if (err) {
        console.error(err);
        process.exit(1);
    }
});