seq-prom
Version:
A small library to allow for sequential operations on an array using Promises
289 lines • 10.9 kB
JavaScript
;
/**
* 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SeqPromise = SeqPromise;
/**
* 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;
exports.default = SeqPromise;
//# sourceMappingURL=index.js.map