Menu

Promise en TypeScript: Promise<T>, Promise.all y allSettled

Cómo se tipan las promesas en TypeScript: el tipo Promise<T>, tipar new Promise y resolve, cómo cambia el tipo con then, por qué catch te da any, Promise.all con resultados en tupla, los tipos de resultado de Promise.allSettled y envolver APIs de callbacks en una promesa tipada.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

En TypeScript una promesa tiene el tipo Promise<T>, donde T es el tipo del valor con el que se resuelve. Una función que devuelve una promesa lo declara en su tipo de retorno, y el valor que obtienes de then o de await tiene tipo T.

La mecánica de las promesas (estados, la cola de microtareas, el encadenamiento) es JavaScript normal, y se explica en promesas en JavaScript. Esta página trata de los tipos.

Tipar new Promise y resolve

Cuando new Promise es el valor de retorno de una función con un tipo de retorno declarado, T sale de ahí. Por sí solo, TypeScript no infiere T a partir de las llamadas a resolve, y obtienes Promise<unknown>. Pasa el argumento de tipo de forma explícita:

Dos detalles. resolve se tipa a partir de T, así que resolve("42") en una Promise<number> es un error de compilación. Y resolve() sin argumento solo se permite cuando T incluye void: en una Promise<number> es el error TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).

El lado de reject no tiene tipo. No existe Promise<T, E>: una promesa puede rechazarse con cualquier valor.

then, catch y finally

Cada then devuelve una promesa nueva tipada según lo que devuelve su callback. Si el callback devuelve una promesa, TypeScript la desenvuelve, así que nunca obtienes Promise<Promise<T>>.

El parámetro del callback de catch tiene tipo any en la librería estándar, no unknown. Nada te impide escribir e.message sobre un valor que podría ser un string. Anótalo como unknown, como arriba, y estréchalo antes de usarlo. Un try/catch alrededor de await lo hace mejor: con strict, su variable de catch ya es unknown (consulta manejo de errores).

Promise.all devuelve una tupla

Dado un array literal de promesas de distintos tipos, Promise.all devuelve una promesa de una tupla, en la que cada posición conserva su propio tipo:

Se pueden mezclar valores que no son promesas, que pasan sin cambios. Promise.all se rechaza en cuanto se rechaza una entrada, y los demás resultados se pierden. Promise.race se resuelve o se rechaza con la primera que termine y su tipo es la unión de las entradas; Promise.any se resuelve con el primer éxito y se rechaza con un AggregateError solo si fallan todas las entradas.

Promise.allSettled y su tipo de resultado

Promise.allSettled espera a todas las entradas y nunca se rechaza. Cada resultado es un PromiseSettledResult<T>, una unión que estrechas con el campo status:

r.value solo existe tras la comprobación de status, porque la variante rechazada no tiene value. reason es any, por la misma razón que en catch. El filter usa un predicado de tipo para que el array filtrado tenga el tipo de los resultados cumplidos.

Envolver una API de callbacks en una promesa

Las APIs antiguas informan de los resultados mediante un callback, a menudo al estilo de Node (err, result) => void. Envuélvelas una vez en una función que devuelva una promesa tipada, y el resto del código podrá usar await:

En Node, util.promisify hace esto con las funciones que siguen la convención (err, result), y muchos módulos integrados ya tienen versiones con promesas (node:fs/promises, node:timers/promises).

Errores habituales

  • Devolver T donde la firma dice Promise<T>, o al revés. Una función no async declarada Promise<User> debe devolver una promesa; una función async la devuelve automáticamente.
  • Olvidar manejar una promesa. Una llamada como save(user); sin await, then ni catch compila sin problema, y un rechazo se convierte en un rechazo no manejado (que por defecto termina un proceso de Node). La regla no-floating-promises de typescript-eslint los detecta.
  • Fiarse del tipo de .catch((e) => ...). Su e es any. Anótalo como unknown.
  • Usar new Promise alrededor de algo que ya devuelve una promesa. Basta con devolver la promesa existente o hacer await sobre ella.

Preguntas frecuentes

¿Qué es Promise<T> en TypeScript?

Promise<T> es el tipo de una promesa que se resuelve con un valor de tipo T. Una función que devuelve Promise<string> entrega una promesa cuyo callback de then, o cuyo await, da un string. Una promesa que se resuelve sin valor es Promise<void>.

¿Cómo tipo new Promise en TypeScript?

Pasa el argumento de tipo: new Promise<number>((resolve, reject) => ...). Sin él, TypeScript no puede inferir el valor a partir de las llamadas a resolve y el resultado es Promise<unknown>. Para una promesa que se resuelve sin nada, usa new Promise<void>(resolve => ...) para que se permita resolve() sin argumento.

¿De qué tipo es el error en el catch de una promesa?

any. El parámetro reason del callback de catch tiene tipo any en la librería estándar, porque se puede lanzar o rechazar cualquier cosa. Anótalo tú como unknown (.catch((e: unknown) => ...)) y estréchalo con instanceof Error antes de usarlo.

¿Cómo funciona Promise.all con los tipos en TypeScript?

Promise.all sobre un array literal devuelve un tipo tupla con un elemento por cada entrada, en orden: Promise.all([getUser(), getCount()]) es Promise<[User, number]>, así que al desestructurar cada valor tiene su propio tipo. Sobre un array de un mismo tipo, T[], devuelve Promise<T[]>.

¿Qué diferencia hay entre Promise.all y Promise.allSettled?

Promise.all se rechaza en cuanto se rechaza cualquier entrada. Promise.allSettled siempre se resuelve, con un array de objetos PromiseSettledResult<T>: { status: "fulfilled", value } o { status: "rejected", reason }. Comprueba status para estrechar cada uno.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR