@stackbit/utils
Version:
Stackbit utilities
237 lines • 8.28 kB
JavaScript
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
;