Callbacks y funciones de orden superior en TypeScript | Nicolás Garzón
Una función de orden superior recibe funciones, retorna funciones o ambas. TypeScript relaciona las firmas para conservar información entre cada etapa.
TypeScript
Copiar function mapProducts < TOutput> (
products: readonly Product[ ] ,
transform : ( product: Product) => TOutput,
) : TOutput[ ] {
return products. map ( transform) ;
}
TypeScript
Copiar type ProductPredicate = (
product: Product,
index: number ,
) => boolean ; TypeScript
Copiar function filterProducts (
products: readonly Product[ ] ,
predicate: ProductPredicate,
) : Product[ ] {
return products. filter ( predicate) ;
} TypeScript
Copiar filterProducts ( products, ( product, index) => {
return product. stock > 0 && index < 10 ;
} ) ; Los parámetros se infieren desde el contexto. Anotarlos de nuevo suele ser innecesario.
Una callback puede ignorar argumentos que el llamador ofrece.
TypeScript
Copiar const names = products. map (
( product) => product. name,
) ; La firma de map ofrece también índice y array, pero la callback no tiene que declararlos.
TypeScript
Copiar function byMinimumPrice (
minimum: number ,
) : ( product: Product) => boolean {
return ( product) => product. price >= minimum;
} TypeScript
Copiar const premiumProducts = products. filter (
byMinimumPrice ( 500 ) ,
) ; TypeScript
Copiar function identity < T > ( value: T ) : T {
return value;
}
function apply < TInput, TOutput> (
value: TInput,
operation : ( value: TInput) => TOutput,
) : TOutput {
return operation ( value) ;
} TypeScript
Copiar const length = apply ( "TypeScript" , ( value) => value. length) ;
TypeScript
Copiar function withLogging < TArgs extends unknown [ ] , TResult> (
operation : ( ... args: TArgs) => TResult,
) : ( ... args: TArgs) => TResult {
return ( ... args) => {
console . log ( "Arguments" , args) ;
const result = operation ( ... args) ;
console . log ( "Result" , result) ;
return result;
} ;
} La función conserva la lista de parámetros y el retorno originales.
Una arrow wrapper no conserva automáticamente el contrato de this de un método.
TypeScript
Copiar function withLogging < TThis, TArgs extends unknown [ ] , TResult> (
operation : ( this : TThis, ... args: TArgs) => TResult,
) : ( this : TThis, ... args: TArgs) => TResult {
return function ( this : TThis, ... args: TArgs) {
console . log ( args) ;
return operation . apply ( this , args) ;
} ;
} TypeScript
Copiar type SyncHandler< T > = ( value: T ) => void ;
type AsyncHandler< T > = ( value: T ) => Promise < void > ; No los mezcles sin decidir quién espera la promesa.
TypeScript
Copiar async function notifyAll (
users: readonly User[ ] ,
notify: AsyncHandler< User> ,
) : Promise < void > {
await Promise . all ( users. map ( notify) ) ;
} TypeScript
Copiar type MaybePromise< T > = T | Promise < T > ;
type Hook< T > = ( value: T ) => MaybePromise< void > ; El llamador debe normalizar:
TypeScript
Copiar await hook ( value) ; TypeScript
Copiar function registerHandler (
handler : ( event: MouseEvent) => void ,
) : ( ) => void {
window. addEventListener ( "click" , handler) ;
return ( ) => {
window. removeEventListener ( "click" , handler) ;
} ;
} La API retorna cleanup y preserva la misma identidad del listener.
TypeScript
Copiar type Callback< T > = (
error: Error | null ,
value? : T ,
) => void ; Permite combinaciones ambiguas. Una unión discriminada o Promise suele ofrecer un contrato más claro en APIs nuevas.
TypeScript
Copiar type PricingStrategy = (
product: Product,
customer: Customer,
) => number ; El tipo comunica el rol de la función, no solo sus parámetros.
TypeScript
Copiar type RequestContext = {
userId: string ;
} ;
type Handler< TResult> = (
context: RequestContext,
) => Promise < TResult> ;
function withAuthorization < TResult> (
handler: Handler< TResult> ,
) : Handler< TResult> {
return async ( context) => {
await requirePermission ( context. userId) ;
return handler ( context) ;
} ;
}
Anotar callbacks con any.
Exigir que la callback declare todos los parámetros disponibles.
Ignorar una Promise retornada por una callback async.
Envolver métodos y perder this.
Retornar una función con una firma más amplia de la necesaria.
Mezclar callback y Promise en una misma API.
No devolver cleanup al registrar recursos.
Usar una firma genérica cuando un nombre de dominio sería más claro.
El contexto puede inferir parámetros de callbacks.
Una función puede ignorar argumentos adicionales.
Los generics preservan relaciones entre entrada y salida.
Un wrapper debe conservar parámetros, retorno y this cuando corresponde.
Los callbacks async necesitan un consumidor que espere la Promise.
Nombrar firmas por su rol mejora la API.
¿Por qué forEach(async () => ...) no se vuelve esperable solo porque la callback está tipada como async?
Respuesta Porque el contrato runtime de forEach ignora los retornos de sus callbacks. El tipo de la callback no cambia ese comportamiento de JavaScript.
Overloads modela APIs con varias formas de llamada relacionadas.