prutill
Version:
Environment-agnostic production-ready promise utility library for managing promise stacks and race conditions. Supports Node.js, Deno, and browsers.
146 lines (130 loc) • 4.61 kB
text/typescript
/**
* A builder of promise wrapper function which acts based on lastly added promise.
*
* By default, previously added promises are resolved with the lastly added value,
* so any previously added promise is resolved with latest value.
* This may be changed by setting `resolveAllPrevious` to false.
*
* This solves the problem where multiple same promises are made,
* but previously added didn't resolve yet or some previous may be quicker.
* Especially for React where we creating promises in useEffect hook,
* but we want to act on single last promise.
*
* Example of usage:
* ```
* // it build the single stack function wrapper to stack promises
* const lastPromiseReactor = stackBuilder();
*
* // PromiseX is added to the stack and then function acts on it,
* // once the lastPromiseReactor is called again with different promise,
* // "then" function will act on the lastly added one
* lastPromiseReactor(...PromiseX...).then(...);
* ```
*
* @returns A single stack function wrapper.
*/
export function stackBuilder<T>(resolveAllPrevious = true): (_: Promise<T>) => Promise<T> {
let storedResolveCallbacks: ((_value: T) => void)[] = [];
let storedPromises: Promise<T>[] = [];
return promise => {
storedPromises.push(promise);
const promiseWithId = {
id: storedPromises.length,
promise,
};
return new Promise((resolve, reject) => {
if (resolveAllPrevious) {
storedResolveCallbacks.push(resolve);
}
// act on any promise
promiseWithId.promise.then(result => {
// act on the last promise
if (promiseWithId.id === storedPromises.length) {
if (resolveAllPrevious) {
for (let i = 0; i < storedResolveCallbacks.length; i++) {
storedResolveCallbacks[i]!(result);
}
} else {
resolve(result);
}
storedPromises = [];
storedResolveCallbacks = [];
}
}, reject);
});
};
}
const storedPromiseStacks: Record<string, (_: Promise<unknown>) => Promise<unknown>> = {};
/**
* Provides builded stackBuilder tied to unique key.
*
* @returns A single stack function wrapper by unique key
*/
export function getLastPromise<T>(key: string, promise: Promise<T>, resolveAllPrevious = true): Promise<T> {
if (storedPromiseStacks[key] === undefined) {
storedPromiseStacks[key] = stackBuilder<T>(resolveAllPrevious) as (_: Promise<unknown>) => Promise<unknown>;
}
return storedPromiseStacks[key](promise) as Promise<T>;
}
/**
* A builder of promise wrapper function which acts acording to first finished promise.
*
* By default, previously added promises are resolved with the first resolved value,
* so any previously added promise is resolved with this value as well.
* This may be changed by setting `resolveAllOthers` to false.
*
* This solves the problem where multiple same promises are made and some may be quicker.
* We suppose that first finished promise talk for all added ones and the rest will be finished with same value.
*
* Example of usage:
* ```
* // it build the single stack function wrapper to stack promises
* const promiseInRace = raceBuilder();
*
* // PromiseX is added to the stack and then function acts on it,
* // once the promiseInRace is called again with different promise,
* // "then" function will act based on first finished promise
* promiseInRace(...PromiseX...).then(...);
* ```
*
* @returns A single stack function wrapper.
*/
export function raceBuilder<T>(resolveAllOthers = true): (_: Promise<T>) => Promise<T> {
let storedResolveCallbacks: ((_value: T) => void)[] = [];
let resolving = false;
return promise => {
return new Promise(resolve => {
if (resolveAllOthers) {
storedResolveCallbacks.push(resolve);
}
promise.then(result => {
if (resolving) {
return;
} else {
resolving = true;
}
if (resolveAllOthers) {
for (let i = 0; i < storedResolveCallbacks.length; i++) {
storedResolveCallbacks[i]!(result);
}
} else {
resolve(result);
}
storedResolveCallbacks = [];
resolving = false;
});
});
};
}
const storedPromiseRaces: Record<string, (_: Promise<unknown>) => Promise<unknown>> = {};
/**
* Provides builded raceBuilder tied to unique key.
*
* @returns A single stack function wrapper by unique key
*/
export function getRaceWonPromise<T>(key: string, promise: Promise<T>, resolveAllOthers = true): Promise<T> {
if (storedPromiseRaces[key] === undefined) {
storedPromiseRaces[key] = raceBuilder<T>(resolveAllOthers) as (_: Promise<unknown>) => Promise<unknown>;
}
return storedPromiseRaces[key](promise) as Promise<T>;
}