Introducción
En este artículo, aprenderá a escribir su propia función promisificar desde cero.
La promisificación ayuda a lidiar con las API basadas en devoluciones de llamada mientras mantiene el código consistente con las promesas.
Podríamos simplemente envolver cualquier función new Promise()
y no preocuparnos por eso en absoluto. Pero hacer eso cuando tenemos muchas funciones sería redundante.
Si comprende las promesas y las devoluciones de llamada, entonces aprender a escribir funciones de promesa debería ser fácil. Entonces empecemos.
Pero, ¿alguna vez te has preguntado cómo funciona promisificar?
Lo importante es no dejar de cuestionar. La curiosidad tiene su propia razón de ser.- Albert Einstein
Las promesas se introdujeron en el estándar ECMA-262, sexta edición (ES6) que se publicó en junio de 2015.
Fue una gran mejora con respecto a las devoluciones de llamada, ya que todos sabemos lo ilegible que puede ser el "infierno de devolución de llamada" :)

Como desarrollador de Node.js, debes saber qué es una promesa y cómo funciona internamente, lo que también te ayudará en las entrevistas de JS. No dude en revisarlos rápidamente antes de seguir leyendo.
¿Por qué necesitamos convertir las devoluciones de llamada en promesas?
- Con las devoluciones de llamada, si desea hacer algo de forma secuencial, tendrá que especificar un
err
argumento en cada devolución de llamada, lo cual es redundante. En promises o async-await, puede simplemente agregar un.catch
método o bloque que detectará cualquier error que haya ocurrido en la cadena de promesa. - Con las devoluciones de llamada, no tiene control sobre cuándo se llama, en qué contexto o cuántas veces se llama, lo que puede provocar pérdidas de memoria.
- Usando promesas, controlamos estos factores (especialmente el manejo de errores) para que el código sea más legible y fácil de mantener.
Cómo hacer que las funciones basadas en callback devuelvan una promesa
Hay dos maneras de hacerlo:
- Envuelva la función en otra función que devuelva una promesa. Luego resuelve o rechaza basándose en argumentos de devolución de llamada.
- Promisificación: creamos una función util / helper
promisify
que transformará todas las API basadas en la primera devolución de llamada de error.
Ejemplo: hay una API basada en devolución de llamada que proporciona la suma de dos números. Queremos prometerlo para que devuelva una thenable
promesa.
const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing arguments"), null); } return callback(null, num1 + num2); } getSumAsync(1, 1, (err, result) => { if (err){ doSomethingWithError(err) }else { console.log(result) // 2 } })
Envolver en una promesa
Como puede ver, getSumPromise
delega todo el trabajo a la función original getSumAsync
, proporcionando su propia devolución de llamada que se traduce en promesa resolve/reject
.
Prometer
Cuando necesitamos prometer muchas funciones, podemos crear una función auxiliar promisify
.
¿Qué es la Promisificación?
Promisificación significa transformación. Es una conversión de una función que acepta una devolución de llamada en una función que devuelve una promesa.
Usando Node.js util.promisify()
:
const { promisify } = require('util') const getSumPromise = promisify(getSumAsync) // step 1 getSumPromise(1, 1) // step 2 .then(result => { console.log(result) }) .catch(err =>{ doSomethingWithError(err); })
Entonces parece una función mágica que se está transformando getSumAsync
en lo getSumPromise
que tiene .then
y .catch
métodos
Escribamos nuestra propia función promisificar:
Si observa el paso 1 en el código anterior, la promisify
función acepta una función como argumento, así que lo primero que tenemos que hacer es escribir una función que pueda hacer lo mismo:
const getSumPromise = myPromisify(getSumAsync) const myPromisify = (fn) => {}
Después de eso, getSumPromise(1, 1)
es una llamada de función. Esto significa que nuestro promisify debería devolver otra función que se puede llamar con los mismos argumentos de la función original:
const myPromisify = (fn) => { return (...args) => { } }
En el código anterior, puede ver que estamos difundiendo argumentos porque no sabemos cuántos argumentos tiene la función original. args
será una matriz que contiene todos los argumentos.
Cuando llamas getSumPromise(1, 1)
, en realidad estás llamando (...args)=> {}
. En la implementación anterior, devuelve una promesa. Es por eso que puedes usar getSumPromise(1, 1).then(..).catch(..)
.
Espero que hayas entendido que la función contenedora (...args) => {}
debería devolver una promesa.
Devolver una promesa
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { }) } }
Ahora la parte complicada es cómo decidir cuándo hacer resolve or reject
una promesa.
En realidad, eso lo decidirá la getSumAsync
implementación de la función original : llamará a la función de devolución de llamada original y solo tenemos que definirla. Entonces basándonos en err
y result
lo haremos reject
o resolve
la promesa.
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } }) } }
Nuestro args[]
solo consta de argumentos pasados getSumPromise(1, 1)
excepto la función de devolución de llamada. Por lo tanto, debe agregar customCallback(err, result)
a lo args[]
que getSumAsync
llamará la función original en consecuencia, ya que estamos rastreando el resultado customCallback
.
Empuje customCallback a args []
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } args.push(customCallback) fn.call(this, ...args) }) } }
Como puede ver, hemos agregado fn.call(this, args)
, que llamará a la función original en el mismo contexto con los argumentos getSumAsync(1, 1, customCallback)
. Entonces nuestra función promisificar debería poder hacerlo en resolve/reject
consecuencia.
The above implementation will work when the original function expects a callback with two arguments, (err, result)
. That’s what we encounter most often. Then our custom callback is in exactly the right format and promisify
works great for such a case.
But what if the original fn
expects a callback with more arguments likecallback(err, result1, result2, ...)
?
In order to make it compatible with that, we need to modify our myPromisify
function which will be an advanced version.
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, ...results) { if (err) { return reject(err) } return resolve(results.length === 1 ? results[0] : results) } args.push(customCallback) fn.call(this, ...args) }) } }
Example:
const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing dependencies"), null); } const sum = num1 + num2; const message = `Sum is ${sum}` return callback(null, sum, message); } const getSumPromise = myPromisify(getSumAsync) getSumPromise(2, 3).then(arrayOfResults) // [6, 'Sum is 6']
That’s all! Thank you for making it this far!
I hope you’re able to grasp the concept. Try to re-read it again. It’s a bit of code to wrap your head around, but not too complex. Let me know if it was helpful ?
Don’t forget to share it with your friends who are starting with Node.js or need to level up their Node.js skills.
References:
//nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original
//github.com/digitaldesignlabs/es6-promisify
You can read other articles like this at 101node.io.