Uma função async em TypeScript sempre retorna uma promise: se o corpo dela retorna uma string, o tipo é Promise<string>. Dentro dela, await desembrulha uma Promise<T> em um T.
O comportamento em tempo de execução (o que o await pausa, o event loop) é JavaScript, coberto em async/await em JavaScript. A parte do TypeScript são os tipos que entram e saem.
Tipos de retorno de funções async
O tipo de retorno declarado de uma função async precisa ser Promise<...>, mesmo que o corpo retorne o valor simples. O TypeScript o infere se você não o escrever.
Escrever async function count(): number dá o erro TS1064 (The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?). O utility type Awaited<T> dá o tipo desembrulhado: Awaited<ReturnType<typeof count>> é number.
Esquecendo o await
Um await que falta deixa você com uma Promise<T> em vez de um T. O TypeScript pega a maioria desses casos porque os tipos deixam de encaixar:
O bug do if (ok) é real: um objeto promise é sempre truthy, então o acesso foi concedido. O TypeScript o reporta como TS2801. Atribuir a promise a uma variável boolean, ou ler uma propriedade que a promise não tem, também não compilaria. Uma chamada cujo resultado você ignora (save(user);) não é pega; a regra no-floating-promises do typescript-eslint cobre esse caso.
Tratamento de erros com try/catch
Uma promise rejeitada faz o await lançar um erro, então o try/catch comum funciona. Com strict, a variável do catch é unknown, e você a estreita antes de ler .message:
Um throw dentro de uma função async rejeita a promise dela em vez de lançar o erro no ponto da chamada. Mais padrões, incluindo classes de erro personalizadas e retornar resultados em vez de lançar erros, estão na página de tratamento de erros.
Top-level await
await fora de qualquer função só funciona em um módulo ES. Um arquivo compilado como CommonJS (o caso destes exemplos, e de projetos Node sem "type": "module") o rejeita:
index.ts(2,14): error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level.
Envolva o código em uma função main async e chame-a, como todos os exemplos desta página fazem. Em um projeto com módulos ES ("type": "module" no package.json com module definido como node16 ou nodenext, ou module: "esnext" para um bundler), o await no nível superior é permitido.
Sequencial vs paralelo
Cada await espera a sua promise antes de a próxima linha começar. Para chamadas independentes, inicie todas primeiro e aguarde-as juntas com Promise.all:
Na metade sequencial, b não consegue terminar antes de a porque ainda não começou. Na metade paralela, d termina primeiro, e o tempo total é mais ou menos o da chamada mais longa, não a soma. Promise.all ainda retorna os resultados na ordem de entrada, tipados como uma tupla.
A armadilha do forEach
forEach ignora a promise que um callback async retorna, então nada espera o trabalho:
for...of com await processa os itens um de cada vez; Promise.all com map os executa em paralelo e espera todos. forEach não faz nenhum dos dois, e o TypeScript não avisa, porque um callback tipado para retornar void aceita um que retorna uma promise.
Iteração assíncrona com for await
for await...of percorre um iterável assíncrono, como um async generator, aguardando cada valor:
async function* pages(total: number): AsyncGenerator<string[]> {
for (let page = 1; page <= total; page++) {
await new Promise((r) => setTimeout(r, 10));
yield [`item ${page}a`, `item ${page}b`];
}
}
async function main() {
for await (const batch of pages(3)) {
console.log(batch.join(", ")); // batch: string[]
}
}
main();
O tipo do elemento vem da anotação AsyncGenerator<T> do generator, ou da inferência quando você não a escreve.
Perguntas frequentes
Qual é o tipo de retorno de uma função async no TypeScript?
Sempre uma promise. Uma função async que retorna um number tem o tipo de retorno Promise<number>, e uma que não retorna nada tem Promise<void>. Escrever async function f(): number dá o erro TS1064, que sugere Promise<number>.
Como usar await no nível superior no TypeScript?
O await no nível superior só funciona em um módulo ES, com module definido como es2022, esnext, system, preserve, ou node16/node18/node20/nodenext em um arquivo que o Node trata como ESM, e target em es2017 ou superior. Em um arquivo CommonJS, é o erro TS1309. A solução portável é uma função main async: async function main() { ... } main();.
Como tratar erros com async/await no TypeScript?
Envolva o await em try/catch. Com strict, o valor capturado é tipado como unknown, então estreite-o antes: if (e instanceof Error) console.log(e.message). Uma rejeição que nunca é aguardada nem capturada vira uma unhandled rejection.
Como executar chamadas async em paralelo no TypeScript?
Inicie todas as promises primeiro e depois aguarde todas juntas: const [a, b] = await Promise.all([loadA(), loadB()]). Escrever await loadA(); await loadB(); executa uma depois da outra. Promise.all mantém o tipo de cada resultado na tupla resultante.
Por que async não funciona dentro de forEach?
forEach chama o callback e ignora o que ele retorna, então as promises de um callback async nunca são aguardadas: o loop termina na hora e o código depois dele roda antes de o trabalho acabar. Use for...of com await para trabalho um por um, ou await Promise.all(items.map(async (x) => ...)) para trabalho em paralelo.