function error(err, extra){ if (!err) return null; err = (err.message) ? err : new Error(err); return Object.assign(err, {extra}); } function isPromise(result) { return result && result.then && (typeof result.then === 'function'); } function wrapCb(callback, opts) { let alreadyCalledBack = false; if (opts.timeout){ setTimeout(function () { if (!alreadyCalledBack) { callback(error(`504: Timeout exceeded`)); } }, opts.timeout); } return (err, ...results) => { // prevent if error have had occurred if (!alreadyCalledBack) { alreadyCalledBack = true; callback(error(err), ...results); } } } export function async(operations, callback, opts={}) { const queue = [...operations]; const cb = wrapCb(callback, opts); function doNext(err, previousResult) { if (err){ cb(err); return; } if (!queue.length){ cb(null, previousResult); return; } const op = queue.shift(); try { if (Array.isArray(op)) { asyncMap(op, (err, ...results)=>doNext(err, results)); } else { //invoke current operation const result = op(previousResult, doNext); // result by return if (result !== undefined) { if (isPromise(result)) { result.then(r=>doNext(null, r), cb); } else { doNext(null, result); } } } } catch (ex) { cb(error(ex,op)); } } doNext(null, doNext); }; export function asyncMap (arr, functor, callback, opts={}) { callback = wrapCb(callback, opts); // counter let countDown = 1; // results const results = []; const createCb = function (pos) { countDown++; return function (err, result) { if (err) { // handle current error callback(err); } else { // ensure idempotence if (results[pos] !== undefined) { return; } // store explicit result results[pos] = result; // check if all done countDown--; if (countDown === 0) { callback(null, ...results); } } } }; for (let datum of arr) { let cb = createCb(results.length); results.push(undefined); try { let result = functor(datum, cb); // result by return if (result !== undefined) { if (isPromise(result)) { result.then(r=>cb(null, r), cb); } else { cb(null, result); } } } catch (ex) { callback(ex); } } // balance inc/dec countDown--; if (countDown === 0) { callback(null, ...results); } } export default async;