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