Problema
Se busca implementar una función debounce
en TypeScript que devuelva el mismo tipo que la función pasada como argumento. Esto implica que si la función original retorna void
o Promise<void>
, el tipo de la función debounced también debe reflejar esto. Sin embargo, se encuentra que TypeScript por defecto infiere el tipo de retorno de la función debounced como void
incluso cuando se pasa una función que debería retornar Promise<void>
.
Implementación original
La implementación original incluye definiciones para dos tipos de funciones: VoidFunction
y PromiseVoidFunction
, junto con una función debounce
que intenta manejar ambos casos. Se utiliza un temporizador para controlar cuándo se llama a la función original, y se intenta resolver la promesa si la función original es asíncrona.
Solución
Para resolver el problema de la inferencia de tipos, se debe ajustar la implementación del tipo de retorno de la función debounced. Esto se puede lograr mediante el uso de un tipo de condición en TypeScript que determina el tipo de retorno basado en el tipo de función original. Aquí se presenta la solución corregida:
type VoidFunction = (...args: any[]) => void;
type PromiseVoidFunction = (...args: any[]) => Promise<void>;
export function debounce<T extends VoidFunction>(fn: T, delay: number): (...args: Parameters<T>) => void;
export function debounce<T extends PromiseVoidFunction>(fn: T, delay: number): (...args: Parameters<T>) => Promise<void>;
export function debounce<T extends (VoidFunction | PromiseVoidFunction)>(fn: T, delay: number) {
let timer: number | null = null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>): T extends PromiseVoidFunction ? Promise<void> : void {
if (timer) {
clearTimeout(timer);
}
if (fn.constructor.name === 'AsyncFunction') {
return new Promise<void>((resolve) => {
timer = setTimeout(() => {
timer = null;
Promise.resolve(fn.apply(this, args)).then(resolve);
}, delay);
}) as any; // El casting aquí es necesario para mantener la inferencia correcta
}
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, delay);
} as any; // El casting aquí es necesario para mantener la inferencia correcta
};
Conclusión
Con estos ajustes, la función debounce
ahora devuelve el tipo correcto según la función que se le pasa. Al usar el tipo condicional en la declaración de retorno dentro de la última función, TypeScript es capaz de inferir adecuadamente si el tipo de retorno es void
o Promise<void>
según el tipo de la función original. Esto resuelve el problema de la inferencia de tipos en la implementación original.