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);
}
});