UNPKG

spex

Version:

Specialized Promise Extensions

189 lines (176 loc) 7.76 kB
'use strict'; /** * @method sequence * @summary Resolves a dynamic sequence of $[mixed values]. * @description * Acquires $[mixed values] from the source function, one at a time, and resolves them, * till either no more values left in the sequence or an error/reject occurs. * It supports both [linked and detached sequencing](../concept/sequencing.md). * @param {function} source * Expected to return the next $[mixed value] to be resolved. If the value resolves * into `undefined`, it signals the end of the sequence, and the method resolves. * * Parameters: * - `index` = current request index in the sequence * - `data` = resolved data from the previous call (`undefined` when `index=0`) * - `delay` = number of milliseconds since the last call (`undefined` when `index=0`) * * The function is called with the same `this` context as the calling method. * * If the function throws an error or returns a rejected promise, the sequence terminates, * and the method rejects with object `{index, error, source}`: * - `index` = index of the request that failed * - `error` = the error thrown or the rejection reason * - `source` = resolved `data` that was passed into the function * * Passing in anything other than a function will throw `Invalid sequence source.` * * @param {function} [dest] * Optional destination function (notification callback), to receive resolved data for each index, * process it and respond as required. * * Parameters: * - `index` = index of the resolved data in the sequence * - `data` = the data resolved * - `delay` = number of milliseconds since the last call (`undefined` when `index=0`) * * The function is called with the same `this` context as the calling method. * * It can optionally return a promise object, if data processing is done asynchronously. * If a promise is returned, the method will not request the next value from the `source` function, * until the promise has been resolved. * * If the function throws an error or returns a promise that rejects, the sequence terminates, * and the method rejects with object `{index, error, dest}`: * - `index` = index of the data that was processed * - `error` = the error thrown or the rejection reason * - `dest` = resolved data that was passed into the function * * Passing in a non-empty value other than a function will throw `Invalid sequence destination.` * * @param {Number} [limit=0] * Limits the maximum size of the sequence. If the value is an integer greater than 0, * the method will successfully resolve once the specified limit has been reached. * By default, the sequence is unlimited, and will continue till either `source` function * returns `undefined` or an error/reject occurs. * * @param {Boolean} [track=false] * This parameter changes the type of data to be resolved by this method. * By default, it is `false` (see the return result). * When set to be `true`, it instructs the method to track/collect all resolved data into * an array internally, so it can be resolved with once the method has finished successfully. * * It must be used with caution, as to the size of the sequence, because accumulating data for * a very large sequence can result in consuming too much memory. * * @returns {Promise} * When successful, the resolved data depends on parameter `track`. When `track` is `false` * (default), the method resolves with object `{total, duration}`: * - `total` = number of values resolved by the sequence * - `duration` = number of milliseconds consumed by the method * * When `track` is `true`, the method resolves with an array of all the data that has been resolved, * the same way that the standard `promise.all` resolves. In addition, the array comes extended with * read-only property `duration` - number of milliseconds consumed by the method. * * When the method fails, the reject result depends on which function caused the failure - `source` * or `dest`. See the two parameters for the rejection details. */ function sequence(source, dest, limit, track) { if (typeof source !== 'function') { throw new TypeError("Invalid sequence source."); } if (!$utils.isNull(dest) && typeof dest !== 'function') { throw new TypeError("Invalid sequence destination."); } limit = (parseInt(limit) === limit && limit > 0) ? limit : 0; var self = this, data, srcTime, destTime, result = [], start = Date.now(); return $p(function (resolve, reject) { function loop(idx) { var srcNow = Date.now(), srcDelay = idx ? (srcNow - srcTime) : undefined; srcTime = srcNow; $utils.resolve.call(self, source, [idx, data, srcDelay], function (value, delayed) { data = value; if (data === undefined) { finish(); } else { if (track) { result.push(data); } if (dest) { var destResult, destNow = Date.now(), destDelay = idx ? (destNow - destTime) : undefined; destTime = destNow; try { destResult = dest.call(self, idx, data, destDelay); } catch (e) { reject({ index: idx, error: e, dest: data }); return; } if ($utils.isPromise(destResult)) { destResult .then(function () { next(true); }, function (reason) { reject({ index: idx, error: reason, dest: data }); }); } else { next(delayed); } } else { next(delayed); } } }, function (reason) { reject({ index: idx, error: reason, source: data }); }); function next(delayed) { idx++; if (limit === idx) { finish(); } else { if (delayed) { loop(idx); } else { $p.resolve() .then(function () { loop(idx); }); } } } function finish() { var length = Date.now() - start; if (track) { $utils.extend(result, 'duration', length); } else { result = { total: idx, duration: length } } resolve(result); } } loop(0); }); } var $utils, $p; module.exports = function (config) { $utils = config.utils; $p = config.promise; return sequence; };