Node.js: Promises e Async/Await

Última atualização: 2026-08-26

1. O que você vai aprender



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

JAVASCRIPT
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();
▶ Experimente

▶ Exemplo: Operações em diretórios com fs.promises

JAVASCRIPT
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');
▶ Experimente

▶ Exemplo: util.promisify converte funções de retorno de chamada

JAVASCRIPT
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();
▶ Experimente

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

JAVASCRIPT
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);
  }
}
▶ Experimente

▶ Exemplo: Promise.allSettled — Nunca rejeita

JAVASCRIPT
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));
}
▶ Experimente

▶ Exemplo: Promise.race — Controle de tempo limite

JAVASCRIPT
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);
  }
}
▶ Experimente

▶ Exemplo: Promise.any — Recuperação de um resultado bem-sucedido de várias fontes em uma condição de corrida

JAVASCRIPT
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);
  }
}
▶ Experimente

▶ Exemplo:(2) Mermaid: Comparação da execução do método de encadeamento de promessas

100%
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

JAVASCRIPT
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();
▶ Experimente

▶ Exemplo: Função de tratamento de erros sem try/catch

JAVASCRIPT
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();
▶ Experimente

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

JAVASCRIPT
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);
}
▶ Experimente

▶ Exemplo: Função de controle de concorrência com restrições

JAVASCRIPT
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);
}
▶ Experimente

▶ Exemplo: Como usar a API de downloads simultâneos limitados

JAVASCRIPT
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`);
}
▶ Experimente

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

JAVASCRIPT
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');
▶ Experimente

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.

JAVASCRIPT
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:

TEXT 📖 Somente leitura
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. Use Promise.allSettled quando 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 limitConcurrency que utilize um Set para rastrear as Promises ativas e um Promise.race para controlar o número de operações simultâneas; em ambientes de produção, você também pode usar a biblioteca p-limit.

P: O await só pode ser usado dentro de funções async? R: Sim. O ES2022 introduziu o await de nível superior, que permite que o await seja 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ção async.

P: O util.promisify consegue 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.allSettled retorna 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 async retorna? R: Uma função async sempre retorna uma Promise. Mesmo que retorne um valor comum, ele é automaticamente envolvido em Promise.resolve(value).


📖 Resumo


📝 Exercícios

  1. Conclua todos os exemplos de código desta lição e certifique-se de que cada um deles seja executado corretamente.
  2. Modifique o exemplo completo e adicione suas próprias extensões
  3. 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.
  4. Reflexão: Como você aplicaria o que aprendeu nesta aula a um projeto do mundo real?
  5. Tente combinar o que você aprendeu nesta lição com o conteúdo das lições anteriores para criar um pequeno projeto.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%