123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- const {SequenceError} = require('../errors/sequence');
- /**
- * @method sequence
- * @description
- * Resolves a dynamic sequence of [mixed values]{@tutorial mixed}.
- *
- * The method acquires [mixed values]{@tutorial mixed} 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]{@tutorial sequencing}.
- *
- * @param {Function|generator} source
- * Expected to return the next [mixed value]{@tutorial mixed} to be resolved. Returning or resolving
- * with `undefined` ends 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 inherits `this` context from the calling method.
- *
- * If the function throws an error or returns a rejected promise, the sequence terminates,
- * and the method rejects with {@link errors.SequenceError SequenceError}, which will have property `source` set.
- *
- * Passing in anything other than a function will reject with {@link external:TypeError TypeError} = `Parameter 'source' must be a function.`
- *
- * @param {Object} [options]
- * Optional Parameters.
- *
- * @param {Function|generator} [options.dest=null]
- * Optional destination function (or generator), 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 inherits `this` context from 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 another value from the `source` function,
- * until the promise has been resolved (the resolved value is ignored).
- *
- * If the function throws an error or returns a rejected promise, the sequence terminates,
- * and the method rejects with {@link errors.SequenceError SequenceError}, which will have property `dest` set.
- *
- * @param {Number} [options.limit=0]
- * Limits the maximum size of the sequence. If the value is greater than 0, the method will
- * successfully resolve once the specified limit has been reached.
- *
- * When `limit` isn't specified (default), the sequence is unlimited, and it will continue
- * till one of the following occurs:
- * - `source` either returns or resolves with `undefined`
- * - either `source` or `dest` functions throw an error or return a rejected promise
- *
- * @param {Boolean} [options.track=false]
- * 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`, the method tracks/collects all resolved data
- * into an array internally, and resolves with that array 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 {external: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
- * a hidden read-only property `duration` - number of milliseconds consumed by the method.
- *
- * When the method fails, it rejects with {@link errors.SequenceError SequenceError}.
- */
- function sequence(source, options, config) {
- const $p = config.promise, utils = config.utils;
- if (typeof source !== 'function') {
- return $p.reject(new TypeError('Parameter \'source\' must be a function.'));
- }
- source = utils.wrap(source);
- options = options || {};
- const limit = (options.limit > 0) ? parseInt(options.limit) : 0,
- dest = utils.wrap(options.dest),
- self = this, start = Date.now();
- let data, srcTime, destTime, result = [];
- return $p((resolve, reject) => {
- function loop(idx) {
- const srcNow = Date.now(),
- srcDelay = idx ? (srcNow - srcTime) : undefined;
- srcTime = srcNow;
- utils.resolve.call(self, source, [idx, data, srcDelay], (value, delayed) => {
- data = value;
- if (data === undefined) {
- success();
- } else {
- if (options.track) {
- result.push(data);
- }
- if (dest) {
- const destNow = Date.now(),
- destDelay = idx ? (destNow - destTime) : undefined;
- let destResult;
- destTime = destNow;
- try {
- destResult = dest.call(self, idx, data, destDelay);
- } catch (e) {
- fail({
- error: e,
- dest: data
- }, 3, dest.name);
- return;
- }
- if (utils.isPromise(destResult)) {
- destResult
- .then(() => {
- next(true);
- return null; // this dummy return is just to prevent Bluebird warnings;
- })
- .catch(error => {
- fail({
- error: error,
- dest: data
- }, 2, dest.name);
- });
- } else {
- next(delayed);
- }
- } else {
- next(delayed);
- }
- }
- }, (reason, isRej) => {
- fail({
- error: reason,
- source: data
- }, isRej ? 0 : 1, source.name);
- });
- function next(delayed) {
- if (limit === ++idx) {
- success();
- } else {
- if (delayed) {
- loop(idx);
- } else {
- $p.resolve()
- .then(() => {
- loop(idx);
- return null; // this dummy return is just to prevent Bluebird warnings;
- });
- }
- }
- }
- function success() {
- const length = Date.now() - start;
- if (options.track) {
- utils.extend(result, 'duration', length);
- } else {
- result = {
- total: idx,
- duration: length
- };
- }
- resolve(result);
- }
- function fail(reason, code, cbName) {
- reason.index = idx;
- reject(new SequenceError(reason, code, cbName, Date.now() - start));
- }
- }
- loop(0);
- });
- }
- module.exports = function (config) {
- return function (source, options) {
- return sequence.call(this, source, options, config);
- };
- };
|