W3docs

Promisificação em JavaScript

Aprenda promisificação em JavaScript: envolva callbacks em uma Promise, crie um helper promisify() genérico, lide com múltiplos argumentos e conheça suas limitações. Exemplos executáveis.

O que é Promisificação?

Promisificação é o ato de envolver uma função baseada em callback para que ela retorne uma Promise em vez de receber um callback. Você faz isso uma vez e passa a usar .then(), .catch(), encadeamento e async/await em uma função que nunca foi projetada para isso.

Esta página mostra como envolver uma única API de callback error-first, como criar um helper genérico reutilizável promisify(), como lidar com callbacks que retornam mais de um resultado e os casos em que a promisificação não funciona.

Por que Promisificar?

APIs JavaScript mais antigas e a maior parte da biblioteca padrão do Node.js reportam seus resultados por meio de um callback que você passa como argumento. Esse estilo aninha rapidamente e dispersa o tratamento de erros:

getUser(id, (err, user) => {
  if (err) return handleError(err);
  getOrders(user, (err, orders) => {
    if (err) return handleError(err);
    getTotal(orders, (err, total) => {
      if (err) return handleError(err);
      console.log(total);
    });
  });
});

Se as mesmas funções retornassem promises, a lógica se aplainaria em uma única cadeia linear (ou algumas linhas de await) com um único .catch() para todo o fluxo. A promisificação é a ponte entre esses dois mundos — veja Callbacks e Além para entender o lado dos callbacks.

A Convenção de Callback Error-First

Antes de envolver qualquer coisa, você precisa conhecer a forma que está envolvendo. Os callbacks do Node.js seguem a convenção error-first (ou "estilo Node"): o callback é o último argumento e é chamado como callback(error, result).

  • Em caso de falha, error é um objeto Error e result é undefined.
  • Em caso de sucesso, error é null e result contém o valor.

A promisificação mapeia isso diretamente: um error não-null se torna reject(error), e um result bem-sucedido se torna resolve(result).

Envolvendo uma Única API de Callback

Este é o padrão central. Envolvemos uma função no estilo callback em uma nova Promise, chamando reject para o erro e resolve para o valor. O exemplo simula uma API error-first com setTimeout para que funcione em qualquer lugar, inclusive no navegador:


javascript— editable

O wrapper aceita o mesmo argumento id, o repassa e fornece seu próprio callback que faz a ponte com resolve/reject. O chamador nunca mais precisa lidar com um callback.

Usando o Wrapper com async/await

O verdadeiro benefício de uma função que retorna uma promise é que ela funciona com async/await, transformando código assíncrono em algo que se lê de cima para baixo:


javascript— editable

Um Helper Genérico promisify()

Escrever um wrapper manualmente para cada função se torna repetitivo. Um helper genérico recebe qualquer função error-first e retorna uma versão que retorna uma promise. O truque é coletar todos os argumentos originais com um parâmetro rest e depois adicionar nosso próprio callback:


javascript— editable

Como o helper usa ...args e repassa this, ele funciona com funções que têm qualquer número de argumentos iniciais. No Node.js, a biblioteca padrão oferece exatamente isso como util.promisify, então raramente é necessário escrever o seu próprio:

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

Lidando com Callbacks de Múltiplos Argumentos

O helper simples assume que o callback entrega um único resultado: callback(err, result). Algumas APIs passam vários valores, como callback(err, header, body). Um resolve(result) simples descartaria silenciosamente tudo após o primeiro valor.

Uma promise só pode ser resolvida com um valor, então colete os argumentos extras em um array (ou um object) e resolva com ele:


javascript— editable

O util.promisify do Node suporta a mesma ideia por meio de um símbolo personalizado (util.promisify.custom), mas para funções ad-hoc um array é a abordagem mais simples.

Limitações e Armadilhas

A promisificação é mecânica, mas tem limites reais:

  • Ela espera a convenção error-first. Se uma função sinaliza erros de outra forma — por exemplo, um retorno boolean, uma exceção lançada ou a ordem (result, err) — um helper genérico irá interpretá-la incorretamente. Envolva essas funções manualmente.
  • Ela só lida com uma única conclusão. Promises são resolvidas uma única vez. Uma função que invoca seu callback repetidamente (eventos, streams, setInterval, um callback de progresso) não pode ser promisificada — apenas a primeira chamada resolveria a promise; chamadas posteriores seriam ignoradas. Use uma API de eventos ou um iterador assíncrono para valores repetidos.
  • Você não pode cancelar uma promise. Se a API de callback subjacente suporta cancelamento (como limpar um timer), essa capacidade é perdida quando fica oculta atrás de uma promise.
  • O wrapper altera a assinatura da chamada. Os chamadores agora devem usar .then/await em vez de passar um callback. Não promisifique uma função que algum código ainda chama no estilo callback sem manter ambas as versões.
  • Um throw dentro do executor ainda rejeita. Código que roda de forma síncrona dentro de new Promise((resolve, reject) => { ... }) é capturado e convertido em rejeição — mas um erro lançado depois dentro de um callback assíncrono não é capturado automaticamente, que é exatamente por isso que você deve chamar reject(err) explicitamente.

Boas Práticas

  • Promisifique na fronteira. Converta APIs de I/O e timer uma única vez, próximo ao ponto em que entram no seu código, e mantenha o restante da base de código orientado a promises.
  • Prefira os built-ins. No Node.js, recorra ao util.promisify (ou aos módulos fs/promises, dns/promises, etc.) antes de criar um wrapper manualmente.
  • Sempre trate a rejeição. Adicione um .catch() ou envolva o await em try/catch; uma rejeição não tratada pode derrubar um processo Node.
  • Mantenha nomes previsíveis. Uma convenção comum é adicionar o sufixo Async à versão promise (readFileAsync) para que ambos os estilos possam coexistir.

Tópicos Relacionados

Prática

Prática
Qual é a principal função da promisificação em JavaScript?
Qual é a principal função da promisificação em JavaScript?
Was this page helpful?