W3docs

Promisificación en JavaScript

Aprende la promisificación en JavaScript: envuelve callbacks en una Promise, crea un helper promisify() genérico, gestiona callbacks multi-argumento y conoce sus limitaciones. Ejemplos ejecutables.

¿Qué es la promisificación?

La promisificación consiste en envolver una función basada en callbacks para que devuelva una Promise en lugar de recibir un callback. Se hace una vez y a partir de entonces se puede usar .then(), .catch(), encadenamiento y async/await en una función que nunca fue diseñada para ello.

Esta página explica cómo envolver una API de callback error-first, cómo construir un helper genérico reutilizable promisify(), cómo manejar callbacks que devuelven más de un resultado y los casos en que la promisificación no funciona.

¿Por qué promisificar?

Las API antiguas de JavaScript y la mayor parte de la biblioteca estándar de Node.js comunican sus resultados a través de un callback que se les pasa. Ese estilo se anida rápidamente y dispersa el manejo de errores:

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);
    });
  });
});

Si las mismas funciones devolvieran promises, la lógica se aplanaría en una única cadena lineal (o unas pocas líneas await) con un solo .catch() para todo el flujo. La promisificación es el puente entre estos dos mundos — consulta Callbacks y más allá para conocer el lado de los callbacks.

La convención callback error-first

Antes de envolver cualquier cosa, es necesario conocer la forma que se va a envolver. Los callbacks de Node.js siguen la convención error-first (o "estilo Node"): el callback es el último argumento y se invoca como callback(error, result).

  • En caso de fallo, error es un objeto Error y result es undefined.
  • En caso de éxito, error es null y result contiene el valor.

La promisificación mapea esto directamente: un error distinto de null se convierte en reject(error), y un result exitoso se convierte en resolve(result).

Envolver una API de callback individual

Este es el patrón principal. Se envuelve una función de estilo callback en una nueva Promise, llamando a reject para el error y a resolve para el valor. El ejemplo simula una API error-first con setTimeout para que funcione en cualquier lugar, incluido el navegador:


javascript— editable

El wrapper acepta el mismo argumento id, lo reenvía y proporciona su propio callback que sirve de puente hacia resolve/reject. El código que llama a la función ya no ve ningún callback.

Usar el wrapper con async/await

El verdadero beneficio de una función que devuelve una promise es que funciona con async/await, convirtiendo el código asíncrono en algo que se lee de arriba a abajo:


javascript— editable

Un helper promisify() genérico

Escribir un wrapper a mano para cada función resulta repetitivo. Un helper genérico toma cualquier función error-first y devuelve una versión que retorna una promise. El truco consiste en recopilar todos los argumentos originales con un parámetro rest y luego añadir nuestro propio callback:


javascript— editable

Dado que el helper usa ...args y reenvía this, funciona con funciones que tengan cualquier número de argumentos iniciales. En Node.js, la biblioteca estándar incluye exactamente esto como util.promisify, por lo que rara vez necesitarás escribir el tuyo propio:

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));

Manejar callbacks con múltiples argumentos

El helper simple asume que el callback entrega un único resultado: callback(err, result). Algunas API pasan varios valores, como callback(err, header, body). Un resolve(result) simple descartaría silenciosamente todo lo que venga después del primer valor.

Una promise solo puede resolverse con un valor, así que recoge los argumentos adicionales en un array (o un object) y resuélvelo con eso:


javascript— editable

util.promisify de Node admite la misma idea a través de un símbolo personalizado (util.promisify.custom), pero para funciones ad-hoc un array es el enfoque más sencillo.

Limitaciones y advertencias

La promisificación es mecánica, pero tiene límites reales:

  • Espera la convención error-first. Si una función señala errores de otra manera — por ejemplo, con un retorno boolean, una excepción lanzada o un orden (result, err) — un helper genérico lo interpretará mal. En esos casos, envuélvelas manualmente.
  • Solo gestiona una única finalización. Las promises se resuelven una sola vez. Una función que invoca su callback de forma repetida (eventos, streams, setInterval, un callback de progreso) no puede promisificarse — solo la primera llamada resolvería la promise; las llamadas posteriores se ignoran. Usa una API de eventos o un iterador asíncrono para valores repetidos.
  • No se puede cancelar una promise. Si la API de callback subyacente admite cancelación (como limpiar un temporizador), esa capacidad se pierde una vez que queda oculta tras una promise.
  • El wrapper cambia la firma de llamada. Los invocadores deben usar ahora .then/await en lugar de pasar un callback. No promisifiques una función que algún código sigue llamando en estilo callback sin mantener ambas versiones.
  • Un throw dentro del executor sigue rechazando. El código que se ejecuta de forma síncrona dentro de new Promise((resolve, reject) => { ... }) se captura y se convierte en un rechazo — pero un error lanzado después dentro de un callback asíncrono no se captura automáticamente, razón por la cual debes llamar a reject(err) explícitamente.

Buenas prácticas

  • Promisifica en el límite. Convierte las API de I/O y temporizadores una sola vez, cerca del punto donde entran en tu código, y mantén el resto de tu base de código basada en promises.
  • Prefiere los built-ins. En Node.js, recurre a util.promisify (o a los módulos fs/promises, dns/promises, etc.) antes de escribir un wrapper propio.
  • Maneja siempre el rechazo. Añade un .catch() o envuelve await en try/catch; un rechazo no gestionado puede hacer que un proceso de Node se cierre inesperadamente.
  • Mantén nombres predecibles. Una convención habitual es añadir el sufijo Async a la versión con promise (readFileAsync) para que ambos estilos puedan coexistir.

Temas relacionados

Práctica

Práctica
¿Cuál es la función principal de la promisificación en JavaScript?
¿Cuál es la función principal de la promisificación en JavaScript?
Was this page helpful?