Introdução à programação assíncrona em TypeScript
Programação assíncrona é uma abordagem para escrever código onde operações que consomem tempo (como requisições ao servidor, leitura de arquivos, timers) não bloqueiam a execução do fluxo principal do programa. No desenvolvimento web moderno, assincronicidade não é apenas uma conveniência, mas uma necessidade. JavaScript e TypeScript, sendo linguagens single-threaded, utilizam o Event Loop para processar operações assíncronas.
TypeScript adiciona a este poderoso paradigma a tipagem estática, tornando o código mais previsível e seguro. Neste guia, abordaremos todos os aspectos da programação assíncrona: desde callbacks clássicos até modernos async/await com tipagem completa.
Fundamentos da assincronicidade: de callbacks a promises
O problema dos callbacks e o "callback hell"
Inicialmente, a assincronicidade em JavaScript era implementada através de callbacks (funções de retorno). No entanto, essa abordagem rapidamente levava a código ilegível:
function loadData(callback: (data: string) => void) { setTimeout(() => { callback("Dados carregados"); }, 1000);}
loadData((data) => { console.log(data); // Chamada aninhada loadData((data2) => { console.log(data2); });});Esse código é difícil de depurar e manter. TypeScript não resolve o problema do aninhamento, mas ajuda a tipar os parâmetros dos callbacks.
Promises (Promise) — salvação do inferno
Uma Promise representa um objeto que pode estar em um de três estados: pending (pendente), fulfilled (resolvida com sucesso) ou rejected (rejeitada com erro). TypeScript permite tipar estritamente o tipo de dados que a Promise retornará:
function fetchUser(id: number): Promise<User> { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, name: "João Pedro" }); } else { reject(new Error("ID inválido")); } }, 500); });}
interface User { id: number; name: string;}
// UsofetchUser(1).then(user => { console.log(user.name); // TypeScript sabe que user é do tipo User}).catch(error => { console.error(error.message);});Graças ao genérico Promise<User>, o compilador TypeScript garante que dentro de .then() receberemos um objeto exatamente desse tipo.
Async/Await: assincronicidade elegante
Sintaxe e tipagem
As palavras-chave async e await permitem escrever código assíncrono que se parece com código síncrono. Uma função marcada com async sempre retorna uma Promise:
async function getGreeting(name: string): Promise<string> { return `Olá, ${name}!`;}
// Tipo do valor de retorno — Promise<string>const result: Promise<string> = getGreeting("Mundo");Importante lembrar: se uma função async retornar algo que não seja uma Promise, TypeScript automaticamente a envolve em uma Promise.
Exemplo com lógica real
Vamos considerar um cenário típico — obter dados de uma API e processá-los:
interface Post { userId: number; id: number; title: string; body: string;}
async function fetchPosts(userId: number): Promise<Post[]> { const response = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const data: Post[] = await response.json(); return data;}
async function displayPosts() { try { const posts = await fetchPosts(1); console.log(`Recebidos ${posts.length} posts:`); posts.forEach(post => console.log(`- ${post.title}`));