You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
50 lines
2.4 KiB
50 lines
2.4 KiB
import { nextTick } from './eventLoop';
|
|
import { StringifiableValue, createSerializer } from './serializer';
|
|
|
|
/**
|
|
* Function like this probably already exists on npm, or maybe it can be constructed from lodash tools etc.
|
|
* But I cannot think of any good keywords to find it now, easier to implement it myself.
|
|
*
|
|
* The purpose is: wrap an async function accepting any number of arguments so that even if the wrapped function
|
|
* is called more than once with the same arguments in quick succession, the underlying function will only be
|
|
* executed at most once at a time for every given argument set.
|
|
*
|
|
* This is useful for complex async functions that cannot really be executed concurrently for the same argument set,
|
|
* because of assumptions they make about the outside world not changing (in a way that matter) while they're executing.
|
|
* Which can be useful for e.g. data retrieval from remote api with caching.
|
|
* Another benefit of throttling is reducing the number of times underlying function is executed, which might bring
|
|
* additional performance benefits.
|
|
*
|
|
* Of course this will only work when only one copy of the application is running at the time.
|
|
*
|
|
* @param f Async function to be wrapped
|
|
* @returns Wrapped function, such that if it is called with the same arguments as some of the previous calls
|
|
* that did not yet resolve, it will return the previous promise rather than invoke `f` again.
|
|
*/
|
|
export const throttle = <TArgs extends StringifiableValue[], TResult>(
|
|
f: (...args: TArgs) => Promise<TResult>,
|
|
): ((...args: TArgs) => Promise<TResult>) => {
|
|
const argsSerializer = createSerializer<TArgs>();
|
|
const promises = new Map<string, Promise<TResult>>();
|
|
return (...args) => {
|
|
const promiseKey = argsSerializer.stringify(args);
|
|
if (!promises.has(promiseKey)) {
|
|
promises.set(
|
|
promiseKey,
|
|
(async () => {
|
|
try {
|
|
return await f(...args);
|
|
} finally {
|
|
await nextTick();
|
|
promises.delete(promiseKey);
|
|
}
|
|
})(),
|
|
);
|
|
}
|
|
|
|
// `promises` is guaranteed to have this key here, because we just set it if it wasn't set before,
|
|
// and the body of `finally` is deferred to another event loop iteration.
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return promises.get(promiseKey)!;
|
|
};
|
|
};
|
|
|