asyncThrottle and higher order functions in Typescript
Typing async higher order functions in Typescript is fun
In a previous post Fun with Typescript and Higher Order Functions I wrote about higher order functions and Typescript. It served as a base for the asyncThrottle
function I was writing up until the part where it wasn't :D
Naive implementation for the async version of throttle looked like this:
function asyncThrottle<T extends (...args: any[]) => any>(
func: T,
timeout: number
): T {
let t: NodeJS.Timeout | undefined;
return function replacement(...args: Parameters<T>): ReturnType<T> {
return new Promise<any>((resolve) => {
if (!!t) {
clearTimeout(t);
}
t = setTimeout(() => {
resolve(func(...args));
}, timeout);
});
};
}
Howver, that doesn't work and replacement
shows the following error:
Type '(...args: Parameters<T>) => ReturnType<T>' is not assignable to type 'T'.
'(...args: Parameters<T>) => ReturnType<T>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '(...args: any[]) => any'
The real problem is that the inner function is using a Promise
and needs full typing. We aren't just calling the passed function, we need to construct something using its parameters and return types.
So we need to fix it a bit. We need to change the return type from T
to (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>
. The helper Awaited
is available in the latest version of Typescript, but if you don't have it, it is easy to implement.
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
Second, the Promise<any>
now becomes Promise<Awaited<ReturnType<T>>>
Solution
function asyncThrottle<T extends (...args: any[]) => any>(
func: T,
timeout: number
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
let t: NodeJS.Timeout | undefined;
return function replacement(
...args: Parameters<T>
): Promise<Awaited<ReturnType<T>>> {
return new Promise<Awaited<ReturnType<T>>>((resolve) => {
if (!!t) {
clearTimeout(t);
}
t = setTimeout(() => {
resolve(func(...args));
}, timeout);
});
};
}