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 objetoErroreresulté undefined. - Em caso de sucesso,
errorénulleresultconté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:
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:
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:
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:
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/awaitem 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 chamarreject(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ódulosfs/promises,dns/promises, etc.) antes de criar um wrapper manualmente. - Sempre trate a rejeição. Adicione um
.catch()ou envolva oawaitemtry/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
- JavaScript Promise — o objeto que você está criando ao promisificar.
- Encadeamento de Promises — sequencie chamadas promisificadas de forma limpa.
- Async/Await — a sintaxe que faz funções promisificadas parecerem código síncrono.
- Callbacks e Além — o padrão do qual você está convertendo.
- API de Promise — combine várias chamadas promisificadas com
Promise.alle outros métodos.