Node.js: Promises e Async/Await
Última atualização: 2026-08-26
1. O que você vai aprender
- Principais métodos e casos de uso da API fs.promises
- Diferenças de comportamento e opções entre Promise.all, race, allSettled e any
- Tratamento de erros com async/await e try/catch
- Controle de concorrência: Limitação do número de tarefas assíncronas em execução simultânea
- Casos de uso para execução sequencial, execução paralela e concorrência limitada
- Melhores práticas para chamadas encadeadas de Promise
- O util.promisify converte código no estilo callback em Promises
2. História: O download de dados em massa do Charlie
Charlie era responsável por baixar dados de configuração de 100 APIs externas. No início, ele usou for para fazer as solicitações uma a uma em um loop; cada request levava cerca de 30 segundos, então foram necessários 50 minutos para concluir todas as 100. Mais tarde, ele mudou para Promise.all para fazer solicitações paralelas, reduzindo o tempo total para 30 segundos — mas o servidor imediatamente retornou 429 Too Many Requests. Charlie percebeu que precisava limitar o número de solicitações simultâneas e, por fim, estabeleceu um limite de 5, reduzindo o tempo total para cerca de 10 minutos — uma solução que era eficiente e não acionava a limitação de taxa.
Esta história destaca o principal dilema da programação assíncrona: velocidade versus recursos. Promises e async/await são as ferramentas para gerenciar esse equilíbrio.
3. API fs.promises
A partir da versão 10, o Node.js oferece o fs.promises, que encapsula operações do sistema de arquivos em métodos que retornam Promises, pondo fim ao “inferno dos callbacks”.
(1) Callbacks do fs vs. fs.promises vs. promisify
| Recurso | callbacks do fs | fs.promises | util.promisify(fs.xxx) |
|---|---|---|---|
| Valor de retorno | void; o resultado é passado por meio de um callback | Promise | Promise |
| Tratamento de erros | O primeiro parâmetro da função de retorno de chamada, err |
try/catch ou .catch() |
try/catch ou .catch() |
| Estilo de código | Callbacks aninhados | Assíncrono/await em estrutura plana | Assíncrono/await em estrutura plana |
| Versões disponíveis | Todas as versões | v10+ | v8+ |
| Uso típico | fs.readFile(path, (err, data) => {}) |
await fs.promises.readFile(path) |
const readFile = promisify(fs.readFile); await readFile(path) |
▶ Exemplo: Lendo e gravando arquivos com fs.promises
const fs = require('fs').promises;
async function readAndWrite() {
try {
const data = await fs.readFile('input.txt', 'utf8');
const upper = data.toUpperCase();
await fs.writeFile('output.txt', upper);
console.log('Done');
} catch (err) {
console.error('Error:', err.message);
}
}
readAndWrite();
▶ Exemplo: Operações em diretórios com fs.promises
const fs = require('fs').promises;
async function listFiles(dir) {
try {
await fs.mkdir(dir, { recursive: true });
const files = await fs.readdir(dir);
for (const file of files) {
const stat = await fs.stat(`${dir}/${file}`);
console.log(`${file} - ${stat.size} bytes`);
}
} catch (err) {
console.error(err.message);
}
}
listFiles('./my-dir');
▶ Exemplo: util.promisify converte funções de retorno de chamada
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
async function copy() {
const data = await readFile('source.txt', 'utf8');
await writeFile('dest.txt', data);
console.log('Copied');
}
copy();
4. Métodos de encadeamento de promessas
Quatro métodos estáticos determinam como várias Promises interagem entre si; escolher a combinação errada de métodos pode levar a resultados muito diferentes.
(1) Comparação de quatro métodos de combinação
| Método | Quando todas as operações são bem-sucedidas | Quando ocorre uma falha | Valor de retorno | Usos típicos |
|---|---|---|---|---|
Promise.all |
Retornar toda a matriz de resultados | Rejeitar na primeira falha; rejeitar todo o conjunto | Matriz de resultados | Todas as tarefas devem ser concluídas |
Promise.race |
Retorna o primeiro resultado válido | Rejeita na primeira falha | Valor único | Controle de tempo limite, resposta mais rápida |
Promise.allSettled |
Retornar todos os resultados | Não rejeitar; incluir mensagens de falha | {status, value/reason}[] |
Retornar todos os resultados, independentemente de sucesso ou falha |
Promise.any |
Retorna o primeiro resultado bem-sucedido | Rejeita apenas se todos falharem (AggregateError) | Valor único | Competição entre múltiplas fontes: considera o sucesso mais rápido |
▶ Exemplo: Promise.all — Só é aprovado se todas as promessas forem bem-sucedidas
async function fetchAll() {
const urls = [
'https://api.example.com/a',
'https://api.example.com/b',
'https://api.example.com/c',
];
try {
const results = await Promise.all(
urls.map(url => fetch(url).then(r => r.json()))
);
console.log('All succeeded:', results.length);
} catch (err) {
console.error('One failed:', err.message);
}
}
▶ Exemplo: Promise.allSettled — Nunca rejeita
async function fetchAllSettled() {
const tasks = [
Promise.resolve({ id: 1 }),
Promise.reject(new Error('Server down')),
Promise.resolve({ id: 3 }),
];
const results = await Promise.allSettled(tasks);
const succeeded = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`Succeeded: ${succeeded.length}, Failed: ${failed.length}`);
failed.forEach(r => console.error('Reason:', r.reason.message));
}
▶ Exemplo: Promise.race — Controle de tempo limite
function fetchWithTimeout(url, ms) {
const fetchTask = fetch(url).then(r => r.json());
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
return Promise.race([fetchTask, timeout]);
}
async function demo() {
try {
const data = await fetchWithTimeout('https://api.example.com/slow', 3000);
console.log(data);
} catch (err) {
console.error(err.message);
}
}
▶ Exemplo: Promise.any — Recuperação de um resultado bem-sucedido de várias fontes em uma condição de corrida
async function fastestMirror() {
const mirrors = [
fetch('https://mirror1.example.com/data').then(r => r.json()),
fetch('https://mirror2.example.com/data').then(r => r.json()),
fetch('https://mirror3.example.com/data').then(r => r.json()),
];
try {
const result = await Promise.any(mirrors);
console.log('Fastest response:', result);
} catch (err) {
console.error('All mirrors failed:', err.errors.length);
}
}
▶ Exemplo:(2) Mermaid: Comparação da execução do método de encadeamento de promessas
flowchart TB
subgraph all["Promise.all"]
A1["Task A ✅"] --- A2["Task B ✅"] --- A3["Task C ✅"]
AR["→ [A, B, C] ✅"]
end
subgraph race["Promise.race"]
R1["Task A ⏱ 1s"] --- R2["Task B ⏱ 3s"] --- R3["Task C ⏱ 2s"]
RR["→ A ✅ (Fastest)"]
end
subgraph settled["Promise.allSettled"]
S1["Task A ✅"] --- S2["Task B ❌"] --- S3["Task C ✅"]
SR["→ [{fulfilled:A}, {rejected:B}, {fulfilled:C}] ✅"]
end
subgraph any["Promise.any"]
N1["Task A ❌"] --- N2["Task B ✅ ⏱ 2s"] --- N3["Task C ✅ ⏱ 3s"]
NR["→ B ✅ (Success as Quickly as Possible)"]
end
all --> AR
race --> RR
settled --> SR
any --> NR
style AR fill:#c8e6c9
style RR fill:#c8e6c9
style SR fill:#fff9c4
style NR fill:#c8e6c9
5. Tratamento de erros com async/await
O async/await faz com que o código assíncrono pareça síncrono, e o tratamento de erros segue o mesmo padrão try/catch — mas há algumas armadilhas.
(1) Comparação entre modelos de tratamento de erros
| Padrão | Implementação | Vantagens | Desvantagens |
|---|---|---|---|
| Callback | if (err) { handle } |
Simples e intuitivo | Aninhamento profundo, fácil de deixar passar despercebido |
| Promise.catch() | promise.then().catch() |
Encadeado, reutilizável | Continua sendo complexo quando aninhado |
| async/await + try/catch | try { await } catch {} |
Estilo síncrono, boa legibilidade | Cada await deve ser encapsulado |
| Função de envoltório | const [err, data] = await to(promise) |
Sem try/catch, concisa | Requer a importação de funções auxiliares |
▶ Exemplo: Envolvendo uma função assíncrona com try/catch
const fs = require('fs').promises;
async function safeReadFile(path) {
try {
const data = await fs.readFile(path, 'utf8');
return { ok: true, data };
} catch (err) {
return { ok: false, error: err.message };
}
}
async function main() {
const result = await safeReadFile('missing.txt');
if (!result.ok) {
console.error('Failed:', result.error);
return;
}
console.log('Content:', result.data);
}
main();
▶ Exemplo: Função de tratamento de erros sem try/catch
function to(promise) {
return promise
.then(data => [null, data])
.catch(err => [err, null]);
}
async function main() {
const fs = require('fs').promises;
const [err, data] = await to(fs.readFile('config.json', 'utf8'));
if (err) {
console.error('Read failed:', err.message);
return;
}
console.log('Config:', data);
}
main();
6. Controle de concorrência
(1) Concorrência sequencial x paralela x restrita
| Método de execução | Tempo total (N tarefas, cada uma levando T tempo) | Vantagens | Desvantagens | Cenários adequados |
|---|---|---|---|---|
| Execução sequencial | N × T | Simples, com baixo consumo de recursos | Lenta | Tarefas com dependências |
| Totalmente paralelo | ≈ T | Mais rápido | Alto pico de uso de recursos; pode sofrer limitação | Poucas tarefas independentes |
| Limitado pela concorrência | ≈ N/concorrência × T | Equilibra velocidade e recursos | Um pouco mais complexo de implementar | Grande número de tarefas independentes, limitação de taxa da API |
▶ Exemplo: Execução sequencial
const fs = require('fs').promises;
async function sequential() {
const files = ['a.txt', 'b.txt', 'c.txt'];
const results = [];
for (const file of files) {
const data = await fs.readFile(file, 'utf8');
results.push(data);
}
console.log('Results:', results.length);
}
▶ Exemplo: Função de controle de concorrência com restrições
async function limitConcurrency(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = task().then(result => {
executing.remove(p);
return result;
});
executing.add(p);
results.push(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
▶ Exemplo: Como usar a API de downloads simultâneos limitados
async function fetchApi(url) {
const res = await fetch(url);
return res.json();
}
async function batchFetch() {
const urls = Array.from({ length: 100 }, (_, i) =>
`https://api.example.com/item/${i + 1}`
);
const tasks = urls.map(url => () => fetchApi(url));
const results = await limitConcurrency(tasks, 5);
console.log(`Fetched ${results.length} items`);
}
7. Melhores práticas para encadeamento de promessas
(1) Regras para chamadas em cadeia
| Regra | Descrição | Contraexemplo |
|---|---|---|
| Sempre retorne uma Promise | Garanta o encadeamento | プロミス.then(() => { doSomething() }) Sem retorno |
| Sempre trate os erros | Adicione .catch() no final | Um bloco encadeado sem .catch() resulta em erros não capturados |
| Evite aninhamento | Simplifique cadeias de .then() | .then(() => { return p.then(...) }) |
| Substituir cadeias longas por async/await | Substituir mais de 3 chamadas de .then() por await | Mais de 5 níveis de chamadas de .then() aninhadas |
Observação: um throw em .then() |
será interceptado pelo próximo .catch() |
porque achei que um throw não interromperia o fluxo |
▶ Exemplo: Chamada em cadeia plana
const fs = require('fs').promises;
function processFile(path) {
return fs.readFile(path, 'utf8')
.then(data => data.trim())
.then(data => data.toUpperCase())
.then(data => fs.writeFile('output.txt', data))
.then(() => console.log('Saved'))
.catch(err => console.error('Error:', err.message));
}
processFile('input.txt');
8. Exemplo abrangente: uma ferramenta de processamento de arquivos em lote com limitação de concorrência
Crie uma ferramenta: Leia um diretório → Limite a 3 processos de arquivo simultâneos → Colete os resultados → Gere estatísticas.
const fs = require('fs').promises;
const path = require('path');
async function processFile(filePath) {
const stat = await fs.stat(filePath);
const content = await fs.readFile(filePath, 'utf8');
const lines = content.split('\n').length;
const words = content.split(/\s+/).filter(Boolean).length;
return {
file: path.basename(filePath),
size: stat.size,
lines,
words,
};
}
async function limitConcurrency(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = task().then(result => {
executing.remove(p);
return result;
});
executing.add(p);
results.push(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
async function batchProcessDir(dirPath, concurrency = 3) {
console.log(`Scanning directory: ${dirPath}`);
const files = await fs.readdir(dirPath);
const filePaths = files
.filter(f => f.endsWith('.txt') || f.endsWith('.md') || f.endsWith('.json'))
.map(f => path.join(dirPath, f));
if (filePaths.length === 0) {
console.log('No matching files found.');
return;
}
const tasks = filePaths.map(fp => () => processFile(fp));
const results = await limitConcurrency(tasks, concurrency);
console.log('\n--- File Statistics ---');
console.log('File'.padEnd(20) + 'Size'.padEnd(10) + 'Lines'.padEnd(8) + 'Words');
console.log('-'.repeat(46));
let totalLines = 0;
let totalWords = 0;
let totalSize = 0;
for (const r of results) {
console.log(
r.file.padEnd(20) +
String(r.size).padEnd(10) +
String(r.lines).padEnd(8) +
String(r.words)
);
totalLines += r.lines;
totalWords += r.words;
totalSize += r.size;
}
console.log('-'.repeat(46));
console.log(
'TOTAL'.padEnd(20) +
String(totalSize).padEnd(10) +
String(totalLines).padEnd(8) +
String(totalWords)
);
console.log(`\nProcessed ${results.length} files (concurrency: ${concurrency})`);
}
batchProcessDir('./data', 3).catch(err => console.error('Fatal:', err.message));
Desempenho:
Scanning directory: ./data
--- File Statistics ---
File Size Lines Words
----------------------------------------------
config.json 256 12 42
readme.md 1024 45 312
notes.txt 512 28 178
----------------------------------------------
TOTAL 1792 85 532
Processed 3 files (concurrency: 3)
❓ Perguntas Frequentes
P: O que acontece se uma プロミス falhar em
Promise.all? R: A operação inteira falha imediatamente (falha rápida), e apenas o primeiro error é retornado. UsePromise.allSettledquando precisar de todos os resultados.
P: Como posso limitar o número de operações assíncronas/await simultâneas? R: Implemente uma função
limitConcurrencyque utilize umSetpara rastrear as Promises ativas e umPromise.racepara controlar o número de operações simultâneas; em ambientes de produção, você também pode usar a bibliotecap-limit.
P: O
awaitsó pode ser usado dentro de funçõesasync? R: Sim. O ES2022 introduziu oawaitde nível superior, que permite que oawaitseja usado diretamente no nível superior de um Módulo ES, mas em módulos CommonJS, ele ainda precisa ser envolvido dentro de uma funçãoasync.
P: O
util.promisifyconsegue converter todas as funções de callback? R: Não. Ele funciona apenas com callbacks do tipo “erro primeiro”, ou seja, aqueles no formato(err, result) => {}. Callbacks personalizados com vários argumentos devem ser encapsulados manualmente.
P: Quais são alguns usos práticos do Promise.race? R: O uso mais comum é para controle de tempo limite — colocar uma Promise de negócio em competição com uma Promise de temporizador; ele também pode ser usado para selecionar a resposta mais rápida entre várias solicitações.
P: O
Promise.allSettledretorna os resultados na mesma order que a entrada? R: Sim, a order da array de resultados corresponde estritamente à order da array de Promises de entrada, independentemente de quando cada Promise é resolvida.
P: O que uma função
asyncretorna? R: Uma funçãoasyncsempre retorna uma Promise. Mesmo que retorne um valor comum, ele é automaticamente envolvido emPromise.resolve(value).
📖 Resumo
- Conceitos-chave e como aplicá-los
- Matéria: Os conceitos básicos e o uso do download de dados em lote do Charlie
- Conceitos básicos e uso da API fs.promises
- Conceitos básicos e uso dos métodos de encadeamento de promessas
- Conceitos básicos e uso do tratamento de erros com async/await
- Conceitos básicos e uso do controle de concorrência
- Conceitos fundamentais e melhores práticas para encadeamento de promessas
- Exemplo abrangente: conceitos fundamentais e uso de uma ferramenta de processamento em lote de arquivos com concorrência limitada
📝 Exercícios
- Conclua todos os exemplos de código desta lição e certifique-se de que cada um deles seja executado corretamente.
- Modifique o exemplo completo e adicione suas próprias extensões
- Analise a documentação oficial, identifique 1 ou 2 APIs que não foram abordadas nesta aula e escreva um código de teste para elas.
- Reflexão: Como você aplicaria o que aprendeu nesta aula a um projeto do mundo real?
- Tente combinar o que você aprendeu nesta lição com o conteúdo das lições anteriores para criar um pequeno projeto.