UNPKG

@stackbit/utils

Version:
237 lines 8.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.deferWhileRunning = exports.deferredPromise = exports.findPromise = exports.reducePromise = exports.mapValuesPromise = exports.mapPromise = exports.forEachPromise = void 0; function forEachPromise(array, callback, thisArg) { return new Promise((resolve, reject) => { function next(index) { if (index < array.length) { callback .call(thisArg, array[index], index, array) .then((result) => { if (result === false) { resolve(); } else { next(index + 1); } }) .catch((error) => { reject(error); }); } else { resolve(); } } next(0); }); } exports.forEachPromise = forEachPromise; function mapPromise(array, callback, thisArg) { return new Promise((resolve, reject) => { const results = []; function next(index) { if (index < array.length) { callback .call(thisArg, array[index], index, array) .then((result) => { results[index] = result; next(index + 1); }) .catch((error) => { reject(error); }); } else { resolve(results); } } next(0); }); } exports.mapPromise = mapPromise; async function mapValuesPromise(object, callback, thisArg) { const results = {}; for (const [key, value] of Object.entries(object)) { results[key] = await callback.call(thisArg, value, key, object); } return results; } exports.mapValuesPromise = mapValuesPromise; function reducePromise(array, callback, initialValue, thisArg) { return new Promise((resolve, reject) => { function next(index, accumulator) { if (index < array.length) { callback .call(thisArg, accumulator, array[index], index, array) .then((result) => { next(index + 1, result); }) .catch((error) => { reject(error); }); } else { resolve(accumulator); } } next(0, initialValue); }); } exports.reducePromise = reducePromise; function findPromise(array, callback, thisArg) { return new Promise((resolve, reject) => { function next(index) { if (index < array.length) { callback .call(thisArg, array[index], index, array) .then((result) => { if (result) { resolve(array[index]); } else { next(index + 1); } }) .catch((error) => { reject(error); }); } else { resolve(undefined); } } next(0); }); } exports.findPromise = findPromise; function deferredPromise() { let _resolve; let _reject; const promise = new Promise((resolve, reject) => { _resolve = resolve; _reject = reject; }); // The executor function is called before the Promise constructor returns: // https://262.ecma-international.org/6.0/#sec-promise-executor // so it is safe to use Non-null Assertion Operator "!" return { promise: promise, resolve: _resolve, reject: _reject }; } exports.deferredPromise = deferredPromise; /** * Creates a function that is restricted to invoking `func` serially. Subsequent * calls to the function while the previous `func` call is being resolved, defer * invoking the `func` until the previous call is resolved. The deferred `func` * is invoked with the last arguments provided to the created function. All * subsequent calls to the function are resolved simultaneously with the result * of the last `func` invocation. * * The `options.groupResolver` function allows separating deferred calls into * groups based on the arguments provided to the created function. * * The `options.argsResolver` function allows controlling the arguments passed * to the deferred `func`. * * @example * const defFunc = deferOnceWhileRunning(origFunc); * * defFunc(z) * defFunc(y) ↓ * defFunc(x) ↓ o---------------------------● * ↓ o---------------------------------------● * o--------------------------------● ↑ * ↓ ↑ ↑ * -----o================================●-o================●-----> * ↑ ↑ * origFunc(x) origFunc(z) */ function deferWhileRunning(func, options = {}) { const groupResolver = options.groupResolver ?? (() => 'defaultGroup'); const argsResolver = options.argsResolver ?? (({ nextArgs }) => nextArgs); const thisArg = options.thisArg; const debounceDelay = options.debounceDelay; const debounceMaxDelay = options.debounceMaxDelay; const deferGroups = {}; const invoke = async (group, args) => { try { group.isInvoking = true; return await func.apply(thisArg, args); } finally { group.isInvoking = false; if (group.deferred !== null) { const nextArgsCopy = group.nextArgs; const deferredCopy = group.deferred; group.nextArgs = null; group.deferred = null; try { deferredCopy.resolve(invoke(group, nextArgsCopy)); } catch (e) { deferredCopy.reject(e); } } } }; const debounce = (group, args, debounceWait) => { const now = new Date().getTime(); if (!group.deferred) { group.startWaiting = now; group.deferred = deferredPromise(); } if (group.timeout) { clearTimeout(group.timeout); } group.nextArgs = argsResolver({ nextArgs: args, prevArgs: group.nextArgs }); const wait = debounceMaxDelay ? Math.min(Math.max(0, debounceMaxDelay - (now - group.startWaiting)), debounceWait) : debounceWait; group.timeout = setTimeout(() => { const nextArgsCopy = group.nextArgs; const deferredCopy = group.deferred; group.nextArgs = null; group.deferred = null; group.timeout = null; group.startWaiting = null; try { deferredCopy.resolve(invoke(group, nextArgsCopy)); } catch (e) { deferredCopy.reject(e); } }, wait); return group.deferred.promise; }; return (async (...args) => { const groupId = groupResolver(...args); let group; if (groupId in deferGroups) { group = deferGroups[groupId]; } else { group = { startWaiting: null, timeout: null, isInvoking: false, deferred: null, nextArgs: null }; deferGroups[groupId] = group; } if (!group.isInvoking) { if (typeof debounceDelay !== 'undefined') { return debounce(group, args, debounceDelay); } return invoke(group, args); } if (!group.deferred) { group.deferred = deferredPromise(); } group.nextArgs = argsResolver({ nextArgs: args, prevArgs: group.nextArgs }); return group.deferred.promise; }); } exports.deferWhileRunning = deferWhileRunning; //# sourceMappingURL=promise-utils.js.map