UNPKG

spex

Version:

Specialized Promise Extensions

208 lines (194 loc) 8.63 kB
'use strict'; /** * @method page * @summary Resolves a dynamic sequence of arrays/pages with $[mixed values]. * * @description * * Acquires pages (arrays of $[mixed values]) from the source function, one by one, * and resolves each page as a $[batch], till no more pages left or an error/reject occurs. * * @param {function} source * * Expected to return a $[mixed value] that resolves with next page of data (array of $[mixed values]). * Returning a value that resolves into `undefined` ends the sequence, and the method resolves. * * The function is called with the same `this` context as the calling method. * * Parameters: * - `index` = index of the page being requested * - `data` = previously returned page, resolved as a $[batch] (`undefined` when `index=0`) * - `delay` = number of milliseconds since the last call (`undefined` when `index=0`) * * If the function throws an error or returns a rejected promise, 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 * * And if the function resolves with anything other than an array or `undefined`, the method * rejects with the same object, but with `error` = `Unexpected data returned from the source.` * * Passing in anything other than a function will throw `Invalid page source.` * * @param {function} [dest] * * Optional destination function (notification callback), to receive resolved $[batch] of data * for each page, process it and respond as required. * * Parameters: * - `index` = page index in the sequence * - `data` = page data resolved as a $[batch] * - `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 notifications are handled asynchronously. * And if a promise is returned, the method will not request the next page 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 page passed into the function * - `error` = the error thrown or the rejection reason * - `dest` = resolved $[batch] that was passed into the function * * Passing in a non-empty value other than a function will throw `Invalid page destination.` * * @param {Number} [limit=0] * * Limits the maximum number of pages to be requested from the `source`. 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 the `source` function returns * `undefined`, or when an error/reject occurs. * * @returns {Promise} * * When successful, the method resolves with object `{pages, total, duration}`: * - `pages` = number of pages resolved * - `total` = the sum of all page sizes (total number of values resolved) * - `duration` = number of milliseconds consumed by the method * * When the method fails, there are two types of rejects that may occur: * - *Normal Reject*: when one of the pages failed to resolve as a $[batch] * - *Internal Reject*: caused by either the `source` or the `dest` functions * * *Normal Rejects* provide object `{index, data}`: * - `index` = index of the page rejected by method $[batch] * - `data` = the rejection data from method $[batch] * * *Internal Rejects* provide object `{index, error, [source], [dest]}`: * - `index` = index of the page for which the error/reject occurred * - `error` = the error thrown or the rejection reason * - `source` - set when caused by the `source` function (see `source` parameter) * - `dest` - set when caused by the `dest` function (see `dest` parameter) * * Object for both reject types has method `getError()` to simplify access to the error. * For *Normal Rejects* it will return `data.getErrors()[0]` (see method $[batch]), * and `error` value for the *Internal Rejects*. */ function page(source, dest, limit) { if (typeof source !== 'function') { throw new TypeError("Invalid page source."); } if (!$utils.isNull(dest) && typeof dest !== 'function') { throw new TypeError("Invalid page destination."); } limit = (parseInt(limit) === limit && limit > 0) ? limit : 0; var self = this, request, srcTime, destTime, start = Date.now(), total = 0; 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, request, srcDelay], function (value) { if (value === undefined) { success(); } else { if (value instanceof Array) { $spex.batch(value) .then(function (data) { request = data; total += data.length; if (dest) { var destResult, destNow = Date.now(), destDelay = idx ? (destNow - destTime) : undefined; destTime = destNow; try { destResult = dest.call(self, idx, data, destDelay); } catch (err) { fail({ error: err, dest: data }); return; } if ($utils.isPromise(destResult)) { destResult .then(function () { next(); }, function (reason) { fail({ error: reason, dest: data }); }); } else { next(); } } else { next(); } }, function (reason) { fail({ data: reason }); }); } else { fail({ error: "Unexpected data returned from the source.", source: request }); } } }, function (reason) { fail({ error: reason, source: request }); }); function next() { idx++; if (limit === idx) { success(); } else { loop(idx); } } function success() { resolve({ pages: idx, total: total, duration: Date.now() - start }); } function fail(reason) { reason.index = idx; $utils.extend(reason, 'getError', function () { return ('data' in reason) ? reason.data.getErrors()[0] : reason.error; }); reject(reason); } } loop(0); }); } var $spex, $utils, $p; module.exports = function (config) { $spex = config.spex; $utils = config.utils; $p = config.promise; return page; };