then, catch, finally e encadeamento de promises

1. Fundamentos do Encadeamento com .then()

O encadeamento de promises é uma das características mais poderosas do JavaScript moderno. Cada chamada .then() retorna uma nova promise, permitindo criar cadeias de operações assíncronas sequenciais.

function buscarUsuario(id) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id, nome: 'João' }), 100);
  });
}

function buscarPedidos(usuario) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(['Pedido 1', 'Pedido 2']), 100);
  });
}

// Encadeamento correto - cada then retorna uma promise
buscarUsuario(1)
  .then((usuario) => {
    console.log('Usuário:', usuario.nome);
    return buscarPedidos(usuario); // Retorna promise para próximo then
  })
  .then((pedidos) => {
    console.log('Pedidos:', pedidos);
    return pedidos.length;
  })
  .then((quantidade) => {
    console.log('Total de pedidos:', quantidade);
  });

Passagem de valores entre .then() consecutivos:

// O valor retornado no primeiro then é passado para o segundo
Promise.resolve(10)
  .then((valor) => valor * 2)    // Retorna 20
  .then((valor) => valor + 5)    // Recebe 20, retorna 25
  .then((valor) => console.log(valor)); // 25

Evite Promises aninhadas - prefira encadeamento plano:

// ❌ Errado: Promise Hell
buscarUsuario(1)
  .then((usuario) => {
    buscarPedidos(usuario).then((pedidos) => {
      console.log(pedidos);
    });
  });

// ✅ Correto: Encadeamento plano
buscarUsuario(1)
  .then(buscarPedidos)
  .then(console.log);

2. Captura de Erros com .catch()

O .catch() captura qualquer rejeição que ocorra na cadeia anterior a ele.

function operacaoRisco(deveFalhar) {
  return new Promise((resolve, reject) => {
    if (deveFalhar) {
      reject(new Error('Operação falhou!'));
    } else {
      resolve('Sucesso!');
    }
  });
}

// Erro em qualquer ponto da cadeia é capturado
operacaoRisco(false)
  .then((resultado) => {
    console.log('Primeiro then:', resultado);
    throw new Error('Erro inesperado no primeiro then');
  })
  .then(() => {
    console.log('Este then nunca executa');
  })
  .catch((erro) => {
    console.error('Capturado:', erro.message);
  });

Posicionamento estratégico do .catch():

// Catch no meio da cadeia - permite recuperação
buscarUsuario(1)
  .then((usuario) => {
    return buscarPedidos(usuario).catch((erro) => {
      console.error('Erro ao buscar pedidos:', erro);
      return []; // Valor padrão para continuar a cadeia
    });
  })
  .then((pedidos) => {
    console.log('Pedidos (ou array vazio):', pedidos);
  });

3. Execução Garantida com .finally()

O .finally() executa independentemente da promise ser resolvida ou rejeitada. É ideal para limpeza de recursos.

function conectarBanco() {
  let conexaoAberta = true;

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Simula sucesso ou falha
      if (Math.random() > 0.5) {
        resolve('Dados do banco');
      } else {
        reject(new Error('Falha na conexão'));
      }
    }, 100);
  }).finally(() => {
    // Sempre executa, independente do resultado
    conexaoAberta = false;
    console.log('Conexão fechada');
  });
}

conectarBanco()
  .then((dados) => console.log('Dados:', dados))
  .catch((erro) => console.error('Erro:', erro.message));

.finally() não recebe o valor resolvido nem o erro:

Promise.resolve('Valor secreto')
  .finally((valor) => {
    console.log('Finally não recebe:', valor); // undefined
  })
  .then((valor) => {
    console.log('Then recebe:', valor); // 'Valor secreto'
  });

4. Padrões de Encadeamento em Node.js

Leitura sequencial de arquivos com fs.promises:

const fs = require('fs').promises;

fs.readFile('config.json', 'utf8')
  .then((configData) => {
    const config = JSON.parse(configData);
    return fs.readFile(config.templateFile, 'utf8');
  })
  .then((template) => {
    console.log('Template carregado:', template.substring(0, 50));
    return fs.writeFile('output.txt', template);
  })
  .then(() => {
    console.log('Arquivo salvo com sucesso');
  })
  .catch((erro) => {
    console.error('Erro no pipeline:', erro.message);
  });

Pipeline de transformação de dados:

const fetch = require('node-fetch');

fetch('https://api.exemplo.com/usuarios')
  .then((resposta) => {
    if (!resposta.ok) throw new Error('Falha na requisição');
    return resposta.json();
  })
  .then((dados) => {
    // Processa dados
    return dados.filter((user) => user.ativo);
  })
  .then((usuariosAtivos) => {
    // Salva no banco
    return salvarNoBanco(usuariosAtivos);
  })
  .then(() => {
    console.log('Pipeline concluído');
  })
  .catch((erro) => {
    console.error('Pipeline falhou:', erro);
  });

5. Encadeamento em React com Hooks

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    setError(null);

    fetch(`https://api.exemplo.com/users/${userId}`)
      .then((response) => {
        if (!response.ok) throw new Error('Usuário não encontrado');
        return response.json();
      })
      .then((data) => {
        setUser(data);
      })
      .catch((err) => {
        setError(err.message);
      })
      .finally(() => {
        setLoading(false); // Garantido executar
      });

    // Cleanup function
    return () => {
      // Limpeza se necessário
    };
  }, [userId]);

  if (loading) return <div>Carregando...</div>;
  if (error) return <div>Erro: {error}</div>;
  return <div>{user?.name}</div>;
}

6. Combinação de .catch() e .finally() em Cadeias Complexas

function processarPagamento(valor) {
  return new Promise((resolve, reject) => {
    if (valor <= 0) reject(new Error('Valor inválido'));
    else resolve({ status: 'aprovado', valor });
  });
}

processarPagamento(100)
  .then((pagamento) => {
    console.log('Pagamento aprovado:', pagamento.valor);
    return debitarConta(pagamento.valor);
  })
  .catch((erro) => {
    if (erro.message === 'Valor inválido') {
      console.error('Erro de validação:', erro.message);
      return Promise.reject(erro); // Propaga para próximo catch
    }
    console.error('Erro no débito:', erro.message);
    return reembolsar(pagamento.valor);
  })
  .catch((erro) => {
    console.error('Erro crítico:', erro.message);
  })
  .finally(() => {
    console.log('Processo finalizado');
    fecharConexao();
  });

7. Boas Práticas e Armadilhas Comuns

❌ Não esquecer de retornar a Promise dentro de .then():

// ❌ Errado - não retorna a promise
buscarUsuario(1)
  .then((usuario) => {
    buscarPedidos(usuario); // Promise não retornada!
  })
  .then((pedidos) => {
    console.log(pedidos); // undefined
  });

// ✅ Correto
buscarUsuario(1)
  .then((usuario) => buscarPedidos(usuario)) // Retorna a promise
  .then((pedidos) => console.log(pedidos));

Usar Promise.resolve() e Promise.reject() para iniciar cadeias:

Promise.resolve()
  .then(() => validarDados(input))
  .then((dados) => processarDados(dados))
  .then((resultado) => salvarResultado(resultado))
  .catch((erro) => console.error(erro));

Promise.reject(new Error('Iniciar com erro'))
  .catch((erro) => {
    console.log('Recuperando do erro:', erro.message);
    return 'Valor de fallback';
  })
  .then((valor) => console.log('Continuou com:', valor));

Armadilha: Erros não capturados em Promises:

// ❌ Erro não capturado - Promise sem catch
new Promise(() => {
  throw new Error('Erro silencioso');
});

// ✅ Sempre adicione um catch final
new Promise(() => {
  throw new Error('Erro capturado');
}).catch((erro) => console.error(erro.message));

Referências