| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 | const {BatchError} = require('../errors/batch');/** * @method batch * @description * Settles (resolves or rejects) every [mixed value]{@tutorial mixed} in the input array. * * The method resolves with an array of results, the same as the standard $[promise.all], * while providing comprehensive error details in case of a reject, in the form of * type {@link errors.BatchError BatchError}. * * @param {Array} values * Array of [mixed values]{@tutorial mixed} (it can be empty), to be resolved asynchronously, in no particular order. * * Passing in anything other than an array will reject with {@link external:TypeError TypeError} = * `Method 'batch' requires an array of values.` * * @param {Object} [options] * Optional Parameters. * * @param {Function|generator} [options.cb] * Optional callback (or generator) to receive the result for each settled value. * * Callback Parameters: *  - `index` = index of the value in the source array *  - `success` - indicates whether the value was resolved (`true`), or rejected (`false`) *  - `result` = resolved data, if `success`=`true`, or else the rejection reason *  - `delay` = number of milliseconds since the last call (`undefined` when `index=0`) * * The function inherits `this` context from the calling method. * * It can optionally return a promise to indicate that notifications are handled asynchronously. * And if the returned promise resolves, it signals a successful handling, while any resolved * data is ignored. * * If the function returns a rejected promise or throws an error, the entire method rejects * with {@link errors.BatchError BatchError} where the corresponding value in property `data` * is set to `{success, result, origin}`: *  - `success` = `false` *  - `result` = the rejection reason or the error thrown by the notification callback *  - `origin` = the original data passed into the callback as object `{success, result}` * * @returns {external:Promise} * * The method resolves with an array of individual resolved results, the same as the standard $[promise.all]. * In addition, the array is extended with a hidden read-only property `duration` - number of milliseconds * spent resolving all the data. * * The method rejects with {@link errors.BatchError BatchError} when any of the following occurs: *  - one or more values rejected or threw an error while being resolved as a [mixed value]{@tutorial mixed} *  - notification callback `cb` returned a rejected promise or threw an error * */function batch(values, options, config) {    const $p = config.promise, utils = config.utils;    if (!Array.isArray(values)) {        return $p.reject(new TypeError('Method \'batch\' requires an array of values.'));    }    if (!values.length) {        const empty = [];        utils.extend(empty, 'duration', 0);        return $p.resolve(empty);    }    options = options || {};    const cb = utils.wrap(options.cb),        self = this, start = Date.now();    return $p((resolve, reject) => {        let cbTime, remaining = values.length;        const errors = [], result = new Array(remaining);        values.forEach((item, i) => {            utils.resolve.call(self, item, null, data => {                result[i] = data;                step(i, true, data);            }, reason => {                result[i] = {success: false, result: reason};                errors.push(i);                step(i, false, reason);            });        });        function step(idx, pass, data) {            if (cb) {                const cbNow = Date.now(),                    cbDelay = idx ? (cbNow - cbTime) : undefined;                let cbResult;                cbTime = cbNow;                try {                    cbResult = cb.call(self, idx, pass, data, cbDelay);                } catch (e) {                    setError(e);                }                if (utils.isPromise(cbResult)) {                    cbResult                        .then(check)                        .catch(error => {                            setError(error);                            check();                        });                } else {                    check();                }            } else {                check();            }            function setError(e) {                const r = pass ? {success: false} : result[idx];                if (pass) {                    result[idx] = r;                    errors.push(idx);                }                r.result = e;                r.origin = {success: pass, result: data};            }            function check() {                if (!--remaining) {                    if (errors.length) {                        errors.sort();                        if (errors.length < result.length) {                            for (let i = 0, k = 0; i < result.length; i++) {                                if (i === errors[k]) {                                    k++;                                } else {                                    result[i] = {success: true, result: result[i]};                                }                            }                        }                        reject(new BatchError(result, errors, Date.now() - start));                    } else {                        utils.extend(result, 'duration', Date.now() - start);                        resolve(result);                    }                }                return null; // this dummy return is just to prevent Bluebird warnings;            }        }    });}module.exports = function (config) {    return function (values, options) {        return batch.call(this, values, options, config);    };};
 |