UNPKG

seq-prom

Version:

A small library to allow for sequential operations on an array using Promises

288 lines 10.9 kB
/** * Created by adrianbrowning */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; /** * Check if a value should be interpreted as "true" * @param value - Value to check * @returns True if the value is boolean true or the string "true" (case insensitive) */ function isTrue(value) { return (value === true) || (value || '').toLowerCase() === 'true'; } /** * Get the type of a value as a string * @param e - Any value to check the type of * @returns The lowercase type name (e.g., 'string', 'array', 'object', 'function') */ function typeOf(e) { var _a, _b; if (e === null) return 'null'; const match = ({}).toString.call(e).match(/\s([a-zA-Z]+)/); return (_b = (_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : 'unknown'; } /** * Creates an error handler function for a specific item * @template ItemT - Type of the item being processed * @template ReturnT - Type of the expected return value * @param self - Instance of SeqPromiseClass * @param item - The item being processed when the error occurred * @returns A function that handles errors for the specific item */ function errorCallBack(self, item) { return function (reason) { self._errors.push({ item, reason }); if (self.errorCB) { self.errorCB(item, reason); } }; } /** * Configure batch queue processing mode * @template ItemType - Type of items to process * @template ReturnType - Type of the return values * @param self - Instance of SeqPromiseClass to configure */ function buildBatchQueue(self) { function secondary(item) { const p = new Promise(function (resolve, reject) { try { const result = self.cb(item, { resolve, reject, self }); // If the callback returns a Promise, handle it if (result instanceof Promise) { result .then(value => resolve(value)) .catch(reason => reject(reason)); } else if (result !== undefined) { resolve(result); } // If neither, assume the callback will use resolve/reject directly } catch (error) { reject(typeOf(error) === "error" ? error.message : String(error)); } }); p.then(response => { self._responses.push({ item, result: response }); }) .catch(errorCallBack(self, item)); return p; } const createAllPromise = function (items) { let pList = []; for (let i = 0; i < items.length; i++) { pList.push(secondary(items[i])); } return Promise.all(pList); }; const processAllBatches = () => __awaiter(this, void 0, void 0, function* () { const _size = self.size; const loops = Math.ceil(self.list.length / self.size); for (let i = 0; i < loops; i++) { if (self._stopped) break; const batch = self.list.splice(0, _size); yield createAllPromise(batch); } if (self.finalCB) { self.finalCB(self._errors, self._responses); } return [self._errors, self._responses]; }); // Chain the batch processing after the initial promise self.promise = self.promise.then(processAllBatches); self.promise = self.promise .then(() => { if (self.finalCB) self.finalCB(self._errors, self._responses); }) .then(() => [self._errors, self._responses]); } /** * Configure pool queue (streaming) processing mode * @template ItemType - Type of items to process * @template ReturnType - Type of the return values * @param self - Instance of SeqPromiseClass to configure */ function buildPoolQueue(self) { function _poolGenerator() { const item = self.list.shift(); if (!item) return; return createCBPromise(self, item) .then((response) => self._responses.push({ item, result: response })) .catch(errorCallBack(self, item)) .then(_poolGenerator); } function createCBPromise(self, item) { return new Promise((resolve, reject) => { if (self._stopped) return void resolve(undefined); try { // Call the callback and capture its return value const result = self.cb(item, { resolve, reject, self }); // If the callback returns a Promise, handle it if (result instanceof Promise) { return result .then(value => resolve(value)) .catch(reason => errorCallBack(self, item)(reason)); } else if (result !== undefined) { return resolve(result); } // If not, we assume the callback will use resolve/reject directly } catch (error) { //errorCallBack(self, item)(typeOf(error) === "error" ? (error as Error).message : String(error) ); reject(typeOf(error) === "error" ? error.message : String(error)); } return; // return self.cb(item, {resolve, reject, self}); }); } self.promise = self.promise .then(function () { const pool = []; for (let i = 0; i < self.size; i++) { pool.push(_poolGenerator()); } return Promise.all(pool); }) .then(() => { if (self.finalCB) self.finalCB(self._errors, self._responses); }) .then(() => [self._errors, self._responses]); } /** * Creates a deferred promise with externally accessible resolver * @template ItemType - Type of items being processed * @template ReturnType - Type of return values * @returns Object containing both the promise and its resolver */ function createMasterPromise() { let promise; let resolver = null; promise = new Promise(resolve => { resolver = resolve; }); return { resolver, promise, }; } class SeqPromiseClass { /** * SeqPromise class processes items sequentially using promises * @template ItemType - Type of items in the list to process * @template ReturnType - Type returned by the callback * * @param options - The options for setting the chain * @param options.list - The list of items to iterate through asynchronously * @param options.cb - Function called for each item, returning a result or promise * @param options.size - Either - The size of "simulated" thread pool (default: 1)\n - size of the batch (default: 1) * @param options.autoStart - Will start processing immediately if true (default: false) * @param options.useBatch - Switches from Stream mode to batch (default: false) * @param options.context - Context to run functions in (this binding) * @param options.finalCB - Function called when all processing is complete * @param options.errorCB - Function called when an error occurs processing an item */ constructor(options) { if (typeOf(options.list) !== 'array') { throw new Error(`Expecting list to be type Array, found type ${typeOf(options.list)}`); } if (!(typeOf(options.cb) === 'function' || typeOf(options.cb) === 'asyncfunction')) { throw new Error(`Expecting cb to be type Function, found type ${typeOf(options.cb)}`); } this.list = options.list.slice(); this.cb = options.cb; this.finalCB = options.finalCB || function () { }; this.errorCB = options.errorCB || function () { }; this.useBatch = isTrue(options.useBatch); this.size = options.size || 1; this.promise = Promise.resolve(); this._globalPromiseResolver = () => { }; this._stopped = false; this._errors = []; this._responses = []; if (options.context) { this.cb = options.cb.bind(options.context); this.finalCB = options.finalCB ? options.finalCB.bind(options.context) : function () { }; this.errorCB = options.errorCB ? options.errorCB.bind(options.context) : function () { }; } if (!options.useBatch && (options.size && options.size >= options.list.length)) { this.size = options.list.length; } let wrappedMasterPromise = createMasterPromise(); this.promise = wrappedMasterPromise.promise; this._globalPromiseResolver = wrappedMasterPromise.resolver; //Batch if (options.useBatch) buildBatchQueue(this); // buildBatchQueue(this); else buildPoolQueue(this); if (isTrue(options.autoStart)) { this._globalPromiseResolver(); return this; } } start() { this._globalPromiseResolver(); return this; } // noinspection JSUnusedGlobalSymbols stop() { this._stopped = true; } } /** * Factory function to create a SeqPromise instance * @template T - Type of items in the list to process * @template RT - Type returned by the callback (default: any) * * @param options - Configuration options for sequential processing * @returns A configured SeqPromiseClass instance that can be started */ function SeqPromise(options) { return new SeqPromiseClass(options); } // trick borrowed from jQuery so we don't have to use the 'new' keyword SeqPromise.prototype = SeqPromiseClass.prototype; export default SeqPromise; // Export the function but not as a type to avoid conflict export { SeqPromise }; //# sourceMappingURL=index.js.map