Menu

Promise em TypeScript: Promise<T>, Promise.all e allSettled

Como as promises são tipadas em TypeScript: o tipo Promise<T>, como tipar new Promise e resolve, como then muda o tipo, por que catch dá any, Promise.all com resultados em tupla, os tipos de resultado de Promise.allSettled e como envolver APIs de callback em uma promise tipada.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

No TypeScript, uma promise tem o tipo Promise<T>, em que T é o tipo do valor com que ela resolve. Uma função que retorna uma promise declara isso no tipo de retorno, e o valor que você recebe de then ou de await tem o tipo T.

A mecânica das promises (estados, a fila de microtasks, o encadeamento) é JavaScript puro, coberta em promises em JavaScript. Esta página trata dos tipos.

Tipando new Promise e resolve

Quando new Promise é o valor de retorno de uma função com tipo de retorno declarado, o T vem dali. Sozinho, o TypeScript não infere T a partir das chamadas de resolve, e você recebe Promise<unknown>. Passe o argumento de tipo explicitamente:

Dois detalhes. resolve é tipado a partir de T, então resolve("42") em uma Promise<number> é erro de compilação. E resolve() sem argumento só é permitido quando T inclui void: em uma Promise<number> ele dá o erro TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).

O lado do reject não é tipado. Não existe Promise<T, E>: uma promise pode rejeitar com qualquer valor.

then, catch e finally

Cada then retorna uma nova promise tipada pelo que o seu callback retorna. Se o callback retornar uma promise, o TypeScript a desembrulha, então você nunca recebe Promise<Promise<T>>.

O parâmetro do callback de catch é tipado como any na biblioteca padrão, não como unknown. Nada impede você de escrever e.message em um valor que pode ser uma string. Anote-o como unknown, como acima, e estreite antes de usar. try/catch em volta de await se sai melhor: com strict, a variável do catch já é unknown (veja tratamento de erros).

Promise.all retorna uma tupla

Dado um array literal de promises de tipos diferentes, Promise.all retorna uma promise de uma tupla, com cada posição mantendo o seu próprio tipo:

Valores que não são promises podem ser misturados e passam sem mudança. Promise.all rejeita assim que uma entrada rejeita, e os outros resultados se perdem. Promise.race resolve ou rejeita com a primeira a terminar e é tipada como a union das entradas; Promise.any resolve com o primeiro sucesso e rejeita com um AggregateError só se todas as entradas falharem.

Promise.allSettled e o tipo do resultado

Promise.allSettled espera todas as entradas e nunca rejeita. Cada resultado é um PromiseSettledResult<T>, uma union que você estreita com o campo status:

r.value só existe depois da verificação de status, porque a variante rejeitada não tem value. reason é any, pelo mesmo motivo que no catch. O filter usa um type predicate para que o array filtrado seja tipado como resultados cumpridos.

Envolvendo uma API de callback em uma promise

APIs mais antigas informam resultados por meio de um callback, muitas vezes no estilo do Node, (err, result) => void. Envolva-as uma vez em uma função que retorna uma promise tipada, e o resto do código pode usar await:

No Node, util.promisify faz isso para funções que seguem a convenção (err, result), e muitos módulos embutidos já têm versões com promises (node:fs/promises, node:timers/promises).

Erros comuns

  • Retornar T onde a assinatura diz Promise<T>, ou o contrário. Uma função que não é async declarada como Promise<User> precisa retornar uma promise; uma função async retorna uma automaticamente.
  • Esquecer de tratar uma promise. Uma chamada como save(user); sem await, then ou catch compila sem problemas, e uma rejeição vira uma unhandled rejection (que, por padrão, encerra um processo Node). A regra no-floating-promises do typescript-eslint pega esses casos.
  • Confiar no tipo de .catch((e) => ...). O e dele é any. Anote-o como unknown.
  • Usar new Promise em volta de algo que já retorna uma promise. Basta retornar a promise existente ou fazer await nela.

Perguntas frequentes

O que é Promise<T> no TypeScript?

Promise<T> é o tipo de uma promise que resolve com um valor do tipo T. Uma função que retorna Promise<string> devolve uma promise cujo callback de then, ou o await, dá uma string. Uma promise que resolve sem valor é Promise<void>.

Como tipar new Promise no TypeScript?

Passe o argumento de tipo: new Promise<number>((resolve, reject) => ...). Sem ele, o TypeScript não consegue inferir o valor a partir das chamadas de resolve e o resultado é Promise<unknown>. Para uma promise que resolve sem nada, use new Promise<void>(resolve => ...) para que resolve() sem argumento seja permitido.

Qual é o tipo do erro no catch de uma promise?

any. O parâmetro reason do callback de catch é tipado como any na biblioteca padrão, porque qualquer coisa pode ser lançada ou rejeitada. Anote-o você mesmo como unknown (.catch((e: unknown) => ...)) e estreite com instanceof Error antes de usá-lo.

Como Promise.all funciona com tipos no TypeScript?

Promise.all sobre um array literal retorna um tipo tupla com um elemento por entrada, na ordem: Promise.all([getUser(), getCount()]) é Promise<[User, number]>, então a desestruturação dá a cada valor o seu próprio tipo. Sobre um array de um único tipo, T[], ele retorna Promise<T[]>.

Qual a diferença entre Promise.all e Promise.allSettled?

Promise.all rejeita assim que qualquer entrada rejeita. Promise.allSettled sempre resolve, com um array de objetos PromiseSettledResult<T>: { status: "fulfilled", value } ou { status: "rejected", reason }. Verifique status para estreitar cada um.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR