Small Nest.js-based project
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

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)!;
};
};