Introdução à programação assíncrona em JavaScript
JavaScript é uma linguagem de programação single-thread com modelo orientado a eventos não bloqueante. No ambiente Node.js, a programação assíncrona não é apenas uma opção conveniente, mas uma necessidade fundamental. Sem assincronicidade, uma aplicação servidora seria bloqueada a cada requisição ao banco de dados, leitura de arquivo ou chamada de rede, tornando-a inadequada para uso real.
Neste guia, vamos abordar os três principais paradigmas de programação assíncrona em JavaScript: Callback, Promise e async/await. Você aprenderá como o Event Loop funciona, como tratar erros e escrever código legível e performático para Node.js.
1. Callback: a base da assincronicidade
Callback (função de retorno) é uma função passada como argumento para outra função e executada após a conclusão de uma operação assíncrona. Este é o modelo mais antigo e básico de assincronicidade em JavaScript.
Exemplo simples com Callback
const fs = require('fs');
// Leitura assíncrona de arquivo com callbackfs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.error('Erro ao ler arquivo:', err); return; } console.log('Conteúdo do arquivo:', data);});
console.log('Leitura do arquivo iniciada...');// Saída: "Leitura do arquivo iniciada..." aparecerá antes do conteúdo do arquivoNeste exemplo, a função callback será executada somente após o arquivo ser completamente lido. Enquanto isso, o fluxo principal continua sua execução — este é o comportamento não bloqueante.
Callback Hell (Inferno dos callbacks)
A principal desvantagem dos callbacks é a rápida perda de legibilidade com chamadas aninhadas:
// Inferno dos callbacksgetUser(1, (err, user) => { if (err) throw err; getPosts(user.id, (err, posts) => { if (err) throw err; getComments(posts[0].id, (err, comments) => { if (err) throw err; console.log(user.name, posts[0].title, comments); }); });});Esse código é difícil de depurar, manter e estender. Felizmente, o JavaScript moderno oferece soluções muito mais elegantes.
2. Promise: promessa de resultado
Promise (promessa) é um objeto que representa o resultado de uma operação assíncrona, que pode ser concluída (fulfilled), rejeitada (rejected) ou estar pendente (pending). Promises permitem evitar aninhamento e construir cadeias de chamadas.
Criação e uso de Promise
// Função que retorna uma Promisefunction readFilePromise(filename) { return new Promise((resolve, reject) => { const fs = require('fs'); fs.readFile(filename, 'utf8', (err, data) => { if (err) { reject(err); // Erro — promise rejeitada } else { resolve(data); // Sucesso — promise resolvida } }); });}
// Usando a promisereadFilePromise('example.txt') .then(data => { console.log('Conteúdo:', data); return data.length; }) .then(length => { console.log('Tamanho do texto:', length); }) .catch(err => { console.error('Erro:', err); });Métodos Promise.all e Promise.race
Promise.all permite aguardar a conclusão de todas as promises, enquanto Promise.race aguarda a primeira a ser concluída:
const p1 = readFilePromise('file1.txt');const p2 = readFilePromise('file2.txt');const p3 = readFilePromise('file3.txt');
// Aguarda todos os três arquivosPromise.all([p1, p2, p3]) .then(([data1, data2, data3]) => { console.log('Todos os arquivos carregados'); console.log(data1, data2, data3); }) .catch(err => console.error('Erro em um dos arquivos:', err));
// Aguarda o primeiro a ser concluídoPromise.race([p1, p2, p3]) .then(firstResult => { console.log('Primeiro arquivo carregado:', firstResult); });