Introdução à programação assíncrona em JavaScript
JavaScript é uma linguagem de programação single-thread com modelo de I/O não bloqueante. Isso significa que apenas uma operação é executada por vez. No entanto, graças à programação assíncrona, Node.js pode processar milhares de requisições simultaneamente sem bloquear a thread principal. Neste artigo, vamos abordar os conceitos principais de assincronia: callbacks, Promises e a sintaxe async/await.
O que é assincronia e por que ela é necessária?
Imagine que você pede uma pizza. Se você fica no balcão esperando ela ser preparada — isso é código síncrono. Tudo para. A abordagem assíncrona — você faz o pedido e cuida da sua vida. Quando a pizza fica pronta, te chamam. JavaScript funciona exatamente assim: ele não espera a conclusão de uma operação (leitura de arquivo, requisição ao servidor), mas continua executando o próximo código. Quando a operação termina, uma função especial é chamada — o callback.
Em Node.js, a assincronia é especialmente importante porque o servidor precisa atender múltiplos clientes simultaneamente. Sem ela, cada nova requisição bloquearia o funcionamento de toda a aplicação.
Código Síncrono vs Assíncrono: exemplo
Vamos considerar um exemplo simples de leitura de arquivo. A versão síncrona bloqueia a execução:
const fs = require('fs');
// Leitura síncrona — bloqueia a thread
const data = fs.readFileSync('file.txt', 'utf8');
console.log('Dados lidos:', data);
console.log('Este código será executado apenas após a leitura do arquivo');
A versão assíncrona não bloqueia:
const fs = require('fs');
// Leitura assíncrona — não bloqueia
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('Dados lidos:', data);
});
console.log('Este código será executado imediatamente, sem esperar a leitura do arquivo');
Principais formas de trabalhar com assincronia
Em JavaScript (e Node.js), existem três abordagens principais para programação assíncrona. Vamos abordá-las das mais antigas para as modernas.
1. Callback (funções de retorno)
Callback é uma função que é passada para outra função e é chamada após a conclusão de uma operação assíncrona. Este é o método mais básico e antigo.
function getUserData(userId, callback) {
setTimeout(() => {
const user = { id: userId, name: 'Ana' };
callback(null, user); // primeiro argumento — erro (se houver)
}, 1000);
}
getUserData(1, (err, user) => {
if (err) {
console.error('Erro:', err);
return;
}
console.log('Usuário:', user.name);
});
Problema: quando há muitas operações, o código se transforma em "callback hell" — funções profundamente aninhadas, difíceis de ler e depurar.
2. Promise (Promessas)
Promise é um objeto que representa o resultado de uma operação assíncrona. Ele pode estar em um dos três estados:
- pending (pendente) — a operação ainda não foi concluída
- fulfilled (resolvida) — a operação foi concluída com sucesso
- rejected (rejeitada) — a operação foi concluída com erro
Promises resolvem o problema do callback hell e tornam o código mais linear:
function getUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = { id: userId, name: 'Ivan' };
// resolve — sucesso, reject — erro
resolve(user);
// reject(new Error('Usuário não encontrado'));
}, 1000);
});
}
// Usando a cadeia .then()
getUserData(1)
.then(user => {
console.log('Usuário:', user.name);
return user.id;
})
.then(id => console.log('ID:', id))
.catch(err => console.error('Erro:', err))
.finally(() => console.log('Operação concluída'));