Resumen y solución al problema de la función debounce en TypeScript

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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *