¿Cómo convierto una API de devolución de llamada existente en promesas?

721

Quiero trabajar con promesas pero tengo una API de devolución de llamada en un formato como:

1. Carga DOM u otro evento único:

window.onload; // set to callback
...
window.onload = function() {

};

2. Devolución de llamada simple:

function request(onChangeHandler) {
    ...
}
request(function() {
    // change happened
    ...
});

3. Devolución de llamada de estilo de nodo ("nodoback"):

function getStuff(dat, callback) {
    ...
}
getStuff("dataParam", function(err, data) {
    ...
})

4. Una biblioteca completa con devoluciones de llamada de estilo de nodo:

API;
API.one(function(err, data) {
    API.two(function(err, data2) {
        API.three(function(err, data3) {
            ...
        });
    });
});

¿Cómo trabajo con la API en promesas, cómo la "prometo"?

Benjamin Gruenbaum
fuente
Publiqué mi propia respuesta, pero las respuestas sobre cómo hacer esto para una biblioteca específica o en más circunstancias y ediciones también son muy bienvenidas.
Benjamin Gruenbaum
@Bergi Esa es una idea interesante, traté de hacer una respuesta general que utiliza los dos enfoques comunes (constructor de promesas y objeto diferido). Traté de dar las dos alternativas en las respuestas. Estoy de acuerdo en que RTFMing resuelve este problema, pero a menudo nos encontramos con este problema tanto aquí como en el rastreador de errores, así que pensé que había una 'pregunta canónica': creo que RTFMing resuelve aproximadamente el 50% de los problemas en la etiqueta JS: D Si Usted tiene una idea interesante para contribuir en una respuesta o editarla sería muy apreciada.
Benjamin Gruenbaum
¿La creación de un new Promisecomplemento supone una sobrecarga significativa? Quiero envolver todas mis funciones síncronas Noje.js en una Promesa para eliminar todo el código síncrono de mi aplicación Node, pero ¿es esta la mejor práctica? En otras palabras, una función que acepta un argumento estático (por ejemplo, una cadena) y devuelve un resultado calculado, ¿debería envolverlo en una promesa? ... Leí en alguna parte que no deberías tener ningún código síncrono en Nodejs.
Ronnie Royston el
1
@RonRoyston no, no es una buena idea para envolver llamadas síncronas con promesas - Sólo llamadas asincrónicas que pueden realizar E / S
Benjamin Gruenbaum

Respuestas:

744

Las promesas tienen estado, comienzan como pendientes y pueden conformarse con:

  • cumplido, lo que significa que el cálculo se completó con éxito.
  • rechazado, lo que significa que el cálculo falló.

Las funciones de promesa de devolución nunca deberían arrojarse , sino que deberían devolver rechazos. Lanzar desde una función de devolución de promesa lo obligará a usar a } catch { y a .catch. Las personas que usan API prometidas no esperan promesas. Si no está seguro de cómo funcionan las API asíncronas en JS, consulte primero esta respuesta .

1. Carga DOM u otro evento único:

Por lo tanto, crear promesas generalmente significa especificar cuándo se liquidan, es decir, cuándo pasan a la fase cumplida o rechazada para indicar que los datos están disponibles (y se puede acceder a ellos .then).

Con implementaciones de promesas modernas que admiten el Promiseconstructor como las promesas nativas de ES6:

function load() {
    return new Promise(function(resolve, reject) {
        window.onload = resolve;
    });
}

Luego usaría la promesa resultante de la siguiente manera:

load().then(function() {
    // Do things after onload
});

Con bibliotecas que admiten diferido (usemos $ q para este ejemplo aquí, pero también usaremos jQuery más adelante):

function load() {
    var d = $q.defer();
    window.onload = function() { d.resolve(); };
    return d.promise;
}

O con un jQuery como API, enganchando un evento que ocurre una vez:

function done() {
    var d = $.Deferred();
    $("#myObject").once("click",function() {
        d.resolve();
    });
    return d.promise();
}

2. Devolución de llamada simple:

Estas API son bastante comunes ya que bueno ... las devoluciones de llamada son comunes en JS. Veamos el caso común de tener onSuccessy onFail:

function getUserData(userId, onLoad, onFail) { 

Con implementaciones de promesas modernas que admiten el Promiseconstructor como las promesas nativas de ES6:

function getUserDataAsync(userId) {
    return new Promise(function(resolve, reject) {
        getUserData(userId, resolve, reject);
    });
}

Con bibliotecas que admiten diferido (usemos jQuery para este ejemplo aquí, pero también hemos usado $ q arriba):

function getUserDataAsync(userId) {
    var d = $.Deferred();
    getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
    return d.promise();
}

jQuery también ofrece un $.Deferred(fn)formulario, que tiene la ventaja de permitirnos escribir una expresión que emule muy de cerca el new Promise(fn)formulario, de la siguiente manera:

function getUserDataAsync(userId) {
    return $.Deferred(function(dfrd) {
        getUserData(userId, dfrd.resolve, dfrd.reject);
    }).promise();
}

Nota: Aquí explotamos el hecho de que los métodos resolvey los diferidos de jQuery rejectson "desmontables"; es decir. están vinculados a la instancia de jQuery.Deferred (). No todas las bibliotecas ofrecen esta función.

3. Devolución de llamada de estilo de nodo ("nodoback"):

Las devoluciones de llamada de estilo de nodo (devoluciones de nodo) tienen un formato particular donde las devoluciones de llamada son siempre el último argumento y su primer parámetro es un error. Primero promisifiquemos uno manualmente:

getStuff("dataParam", function(err, data) { 

A:

function getStuffAsync(param) {
    return new Promise(function(resolve, reject) {
        getStuff(param, function(err, data) {
            if (err !== null) reject(err);
            else resolve(data);
        });
    });
}

Con los diferidos puede hacer lo siguiente (usemos Q para este ejemplo, aunque Q ahora admite la nueva sintaxis que debería preferir ):

function getStuffAsync(param) {
    var d = Q.defer();
    getStuff(param, function(err, data) {
        if (err !== null) d.reject(err);
        else d.resolve(data);
    });
    return d.promise;   
}

En general, no debe prometer demasiado las cosas manualmente, la mayoría de las bibliotecas de promesas que se diseñaron teniendo en cuenta el Nodo, así como las promesas nativas en el Nodo 8+, tienen un método incorporado para promulgar las devoluciones de nodo. Por ejemplo

var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only

4. Una biblioteca completa con devoluciones de llamada de estilo de nodo:

No hay una regla de oro aquí, los prometes uno por uno. Sin embargo, algunas implementaciones prometedoras le permiten hacer esto de forma masiva, por ejemplo, en Bluebird, convertir una API de retorno de nodo en una API de promesa es tan simple como:

Promise.promisifyAll(API);

O con promesas nativas en Node :

const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
                         .reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

Notas:

  • Por supuesto, cuando está en un .thencontrolador no necesita prometer cosas. Devolver una promesa de un .thenmanejador se resolverá o rechazará con el valor de esa promesa. Lanzar desde un .thencontrolador también es una buena práctica y rechazará la promesa: esta es la famosa promesa de seguridad de lanzamiento.
  • En un onloadcaso real , debe usar en addEventListenerlugar de onX.
Benjamin Gruenbaum
fuente
Benjamin, acepté tu invitación para editar y agregué otro ejemplo de jQuery al caso 2. Necesitará una revisión por pares antes de que aparezca. Espero que te guste.
Roamer-1888
@ Roamer-1888 fue rechazado ya que no lo vi y lo acepté a tiempo. Por lo que vale, no creo que la adición sea demasiado relevante aunque útil.
Benjamin Gruenbaum
2
Benjamin, si o no resolve(), y reject()están escritas para ser reutilizable, aventuro que mi edición sugerida es relevante, ya que ofrece un ejemplo de la forma de jQuery $.Deferred(fn), que es también insuficiente. Si solo se incluye un ejemplo de jQuery, sugiero que debería ser de esta forma en lugar de var d = $.Deferred();etc., ya que se debería alentar a las personas a usar la forma que a menudo se descuida $.Deferred(fn), además, en una respuesta como esta, pone a jQuery más a la par con libs que usan el patrón Revealing Constructor .
Roamer-1888
Je, para ser 100% justo, no sabía que jQuery te dejaba hacer $.Deferred(fn), si editas eso en lugar del ejemplo existente en los próximos 15 minutos, estoy seguro de que puedo tratar de aprobarlo a tiempo :)
Benjamin Gruenbaum
77
Esta es una respuesta genial. Es posible que desee actualizarlo mencionando también util.promisifyque Node.js se agregará a su núcleo a partir de RC 8.0.0. Su funcionamiento no es muy diferente al de Bluebird Promise.promisify, pero tiene la ventaja de no requerir dependencias adicionales, en caso de que solo quiera Promise nativo. He escrito una publicación de blog sobre util.promisify para cualquiera que quiera leer más sobre el tema.
Bruno
55

Hoy, puedo usar Promiseen Node.jsun método sencillo Javascript.

Un ejemplo simple y básico para Promise(con forma de KISS ):

Código de API asíncrono Javascript simple :

function divisionAPI (number, divider, successCallback, errorCallback) {

    if (divider == 0) {
        return errorCallback( new Error("Division by zero") )
    }

    successCallback( number / divider )

}

Promise Código API asíncrono Javascript:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            return rejected( new Error("Division by zero") )
        }

        fulfilled( number / divider )

     })

}

(Recomiendo visitar esta hermosa fuente )

También Promisese puede usar junto async\awaitcon ES7para hacer que el flujo del programa espere un fullfiledresultado como el siguiente:

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


async function foo () {

    var name = await getName(); // awaits for a fulfilled result!

    console.log(name); // the console writes "John Doe" after 3000 milliseconds

}


foo() // calling the foo() method to run the code

Otro uso con el mismo código usando el .then()método

function getName () {

    return new Promise(function (fulfilled, rejected) {

        var name = "John Doe";

        // wait 3000 milliseconds before calling fulfilled() method
        setTimeout ( 
            function() {
                fulfilled( name )
            }, 
            3000
        )

    })

}


// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name){ console.log(name) })

Promisetambién se puede usar en cualquier plataforma basada en Node.js como react-native.

Bonificación : un método híbrido
(se supone que el método de devolución de llamada tiene dos parámetros como error y resultado)

function divisionAPI (number, divider, callback) {

    return new Promise(function (fulfilled, rejected) {

        if (divider == 0) {
            let error = new Error("Division by zero")
            callback && callback( error )
            return rejected( error )
        }

        let result = number / divider
        callback && callback( null, result )
        fulfilled( result )

     })

}

El método anterior puede responder al resultado de la devolución de llamada antigua y los usos de Promesa.

Espero que esto ayude.

efkan
fuente
3
Estos no parecen mostrar cómo convertirse en promesas.
Dmitri Zaitsev
33

Antes de convertir una función como promesa en Node.JS

var request = require('request'); //http wrapped module

function requestWrapper(url, callback) {
    request.get(url, function (err, response) {
      if (err) {
        callback(err);
      }else{
        callback(null, response);             
      }      
    })
}


requestWrapper(url, function (err, response) {
    console.log(err, response)
})

Después de convertirlo

var request = require('request');

function requestWrapper(url) {
  return new Promise(function (resolve, reject) { //returning promise
    request.get(url, function (err, response) {
      if (err) {
        reject(err); //promise reject
      }else{
        resolve(response); //promise resolve
      }
    })
  })
}


requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
    console.log(response) //resolve callback(success)
}).catch(function(error){
    console.log(error) //reject callback(failure)
})

En caso de que necesite manejar solicitudes múltiples

var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1')) 
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))    

Promise.all(allRequests).then(function (results) {
  console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
  console.log(err)
});
Sivashanmugam Kannan
fuente
23

No creo que la window.onloadsugerencia de @Benjamin funcione todo el tiempo, ya que no detecta si se llama después de la carga. He sido mordido por eso muchas veces. Aquí hay una versión que siempre debería funcionar:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
León
fuente
1
¿No debería usar la rama "ya completa" setTimeout(resolve, 0)(o setImmediate, si está disponible) para asegurarse de que se llame de forma asincrónica?
Alnitak
55
@Alnitak Llamar resolvesincrónicamente está bien. El marco garantiza que los thencontroladores de Promise se invoquen de forma asíncrona , independientemente de si resolvese llama de forma sincrónica.
Jeff Bowman
15

Node.js 8.0.0 incluye una nueva util.promisify()API que permite que las API de estilo de devolución de llamada estándar de Node.js se envuelvan en una función que devuelve una Promesa. Un ejemplo de uso de util.promisify()se muestra a continuación.

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

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

readFile('/some/file')
  .then((data) => { /** ... **/ })
  .catch((err) => { /** ... **/ });

Ver Soporte mejorado para promesas

Gian Marco Gherardi
fuente
2
Ya hay dos respuestas que describen esto, ¿por qué publicar una tercera?
Benjamin Gruenbaum
1
Solo porque esa versión del nodo ya está disponible, y he informado de la descripción y el enlace de la característica "oficial".
Gian Marco Gherardi
14

En la versión candidata para Node.js 8.0.0, hay una nueva utilidad, util.promisify(he escrito sobre util.promisify ), que encapsula la capacidad de promisificar cualquier función.

No es muy diferente de los enfoques sugeridos en las otras respuestas, pero tiene la ventaja de ser un método central y no requiere dependencias adicionales.

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

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

Entonces tienes un readFilemétodo que devuelve un nativo Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
Bruno
fuente
1
Hola, yo (OP) realmente sugerí util.promisifydos veces (en 2014, cuando se escribió esta pregunta, y hace unos meses, lo que presioné como miembro principal de Node y es la versión actual que tenemos en Node). Como todavía no está disponible públicamente, aún no lo agregué a esta respuesta. Sin embargo, agradeceríamos profundamente los comentarios sobre el uso y conocer cuáles son algunas de las dificultades para tener mejores documentos para el lanzamiento :)
Benjamin Gruenbaum
1
Además, es posible que desee hablar sobre la bandera personalizada para la promesa util.promisifyen su publicación de blog :)
Benjamin Gruenbaum
@BenjaminGruenbaum ¿Se refiere al hecho de que al usar el util.promisify.customsímbolo es posible anular el resultado de util.promisify? Para ser honesto, esta fue una falta intencional, porque todavía no puedo encontrar un caso de uso útil. ¿Quizás me puedan dar algunas entradas?
Bruno
1
Claro, fs.existstenga en cuenta API como API o API que no siguen la convención de Nodo: un bluebird Promise.promisify podría equivocarse, pero las util.promisifyhace bien.
Benjamin Gruenbaum
7

Puede usar las promesas nativas de JavaScript con Node JS.

Enlace del código My Cloud 9: https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
        request.get(url, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                resolve(body);
            }
            else {
                reject(error);
            }
        })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
    //get the post with post id 100
    promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
        var obj = JSON.parse(result);
        return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
    })
    .catch(function (e) {
        console.log(e);
    })
    .then(function (result) {
        res.end(result);
    })
})

var server = app.listen(8081, function () {
    var host = server.address().address
    var port = server.address().port

    console.log("Example app listening at http://%s:%s", host, port)
})

//run webservice on browser : http://localhost:8081/listAlbums
Apoorv
fuente
7

Con el viejo y simple javaScript de vainilla, aquí hay una solución para promisificar una devolución de llamada api.

function get(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url);
        xhr.addEventListener('readystatechange', function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    console.log('successful ... should call callback ... ');
                    callback(null, JSON.parse(xhr.responseText));
                } else {
                    console.log('error ... callback with error data ... ');
                    callback(xhr, null);
                }
            }
        });
        xhr.send();
    }

/**
     * @function promisify: convert api based callbacks to promises
     * @description takes in a factory function and promisifies it
     * @params {function} input function to promisify
     * @params {array} an array of inputs to the function to be promisified
     * @return {function} promisified function
     * */
    function promisify(fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return new Promise(function(resolve, reject) {
                fn.apply(null, args.concat(function (err, result) {
                    if (err) reject(err);
                    else resolve(result);
                }));
            });
        }
    }

var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
        // corresponds to the resolve function
        console.log('successful operation: ', data);
}, function (error) {
        console.log(error);
});
daviddavis
fuente
6

La biblioteca Q de kriskowal incluye funciones de devolución de llamada a promesa. Un método como este:

obj.prototype.dosomething(params, cb) {
  ...blah blah...
  cb(error, results);
}

se puede convertir con Q.ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
Jason Loveman
fuente
1
La respuesta canónica ya menciona Q.denodeify. ¿Necesitamos enfatizar los ayudantes de la biblioteca?
Bergi
3
Encontré esto útil como un google sobre la promesa de Q leads aquí
Ed Sykes
4

Cuando tiene algunas funciones que toman una devolución de llamada y desea que devuelvan una promesa, puede usar esta función para realizar la conversión.

function callbackToPromise(func){

    return function(){

        // change this to use what ever promise lib you are using
        // In this case i'm using angular $q that I exposed on a util module

        var defered = util.$q.defer();

        var cb = (val) => {
            defered.resolve(val);
        }

        var args = Array.prototype.slice.call(arguments);
        args.push(cb);    
        func.apply(this, args);

        return defered.promise;
    }
}
usuario1852503
fuente
4

En el nodo v7.6 + que ha incorporado promesas y asíncrono:

// promisify.js
let promisify = fn => (...args) =>
    new Promise((resolve, reject) =>
        fn(...args, (err, result) => {
            if (err) return reject(err);
            return resolve(result);
        })
    );

module.exports = promisify;

Cómo utilizar:

let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);

async function myAsyncFn(path) {
    let entries = await readdirP(path);
    return entries;
}
Paul Spaulding
fuente
3

En Node.js 8 puede promisificar métodos de objeto sobre la marcha utilizando este módulo npm:

https://www.npmjs.com/package/doasync

Utiliza util.promisify y Proxies para que sus objetos permanezcan sin cambios. La memorización también se realiza con el uso de WeakMaps). Aquí hay unos ejemplos:

Con objetos:

const fs = require('fs');
const doAsync = require('doasync');

doAsync(fs).readFile('package.json', 'utf8')
  .then(result => {
    console.dir(JSON.parse(result), {colors: true});
  });

Con funciones:

doAsync(request)('http://www.google.com')
  .then(({body}) => {
    console.log(body);
    // ...
  });

Incluso puede usar nativo cally applyvincular un contexto:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
Hacer asíncrono
fuente
2

Puede usar Promise nativo en ES6, por ejemplo, lidiar con setTimeout:

enqueue(data) {

    const queue = this;
    // returns the Promise
    return new Promise(function (resolve, reject) {
        setTimeout(()=> {
                queue.source.push(data);
                resolve(queue); //call native resolve when finish
            }
            , 10); // resolve() will be called in 10 ms
    });

}

En este ejemplo, la Promesa no tiene motivos para fallar, por reject()lo que nunca se llama.

Nicolas Zozol
fuente
2

La función de estilo de devolución de llamada siempre es así (casi todas las funciones en node.js son este estilo):

//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))

Este estilo tiene la misma característica:

  1. la última función pasa la función de devolución de llamada.

  2. la función de devolución de llamada siempre acepta el objeto de error como primer argumento.

Entonces, podría escribir una función para convertir una función con este estilo como este:

const R =require('ramda')

/**
 * A convenient function for handle error in callback function.
 * Accept two function res(resolve) and rej(reject) ,
 * return a wrap function that accept a list arguments,
 * the first argument as error, if error is null,
 * the res function will call,else the rej function.
 * @param {function} res the function which will call when no error throw
 * @param {function} rej the function which will call when  error occur
 * @return {function} return a function that accept a list arguments,
 * the first argument as error, if error is null, the res function
 * will call,else the rej function
 **/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
    R.propEq('err', null),
    R.compose(
        res,
        R.prop('data')
    ),
    R.compose(
        rej,
        R.prop('err')
    )
)({err, data})

/**
 * wrap the callback style function to Promise style function,
 * the callback style function must restrict by convention:
 * 1. the function must put the callback function where the last of arguments,
 * such as (arg1,arg2,arg3,arg...,callback)
 * 2. the callback function must call as callback(err,arg1,arg2,arg...)
 * @param {function} fun the callback style function to transform
 * @return {function} return the new function that will return a Promise,
 * while the origin function throw a error, the Promise will be Promise.reject(error),
 * while the origin function work fine, the Promise will be Promise.resolve(args: array),
 * the args is which callback function accept
 * */
 const toPromise = (fun) => (...args) => new Promise(
    (res, rej) => R.apply(
        fun,
        R.append(
            checkErr(res, rej),
            args
        )
    )
)

Para ser más conciso, el ejemplo anterior utilizó ramda.js. Ramda.js es una excelente biblioteca para programación funcional. En el código anterior, utilizamos su aplicación (como javascript function.prototype.apply) y anexar (como javascript function.prototype.push). Entonces, podríamos convertir una función de estilo de devolución de llamada a una función de estilo prometedora ahora:

const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
    .then(
        (files) => console.log(files),
        (err) => console.log(err)
    )

La función toPromise y checkErr es propia de la biblioteca berserk , es una bifurcación de biblioteca de programación funcional de ramda.js (creada por mí).

Espero que esta respuesta te sea útil.

jituanlin
fuente
2

Puedes hacer algo como esto

// @flow

const toPromise = (f: (any) => void) => {
  return new Promise<any>((resolve, reject) => {
    try {
      f((result) => {
        resolve(result)
      })
    } catch (e) {
      reject(e)
    }
  })
}

export default toPromise

Entonces úsalo

async loadData() {
  const friends = await toPromise(FriendsManager.loadFriends)

  console.log(friends)
}
onmyway133
fuente
2
Oye, no estoy seguro de lo que esto agrega a las respuestas existentes (¿tal vez aclarar?). Además, no hay necesidad de probar / atrapar dentro del constructor de promesas (lo hace automáticamente por usted). Tampoco está claro para qué funciones funciona esto (¿eso llama a la devolución de llamada con un solo argumento sobre el éxito? ¿Cómo se manejan los errores?)
Benjamin Gruenbaum
1

es6-promisify convierte funciones basadas en devolución de llamada en funciones basadas en Promesa.

const promisify = require('es6-promisify');

const promisedFn = promisify(callbackedFn, args);

Ref: https://www.npmjs.com/package/es6-promisify

Pujan Srivastava
fuente
1

Mi versión promisificada de una callbackfunción es la Pfunción:

var P = function() {
  var self = this;
  var method = arguments[0];
  var params = Array.prototype.slice.call(arguments, 1);
  return new Promise((resolve, reject) => {
    if (method && typeof(method) == 'function') {
      params.push(function(err, state) {
        if (!err) return resolve(state)
        else return reject(err);
      });
      method.apply(self, params);
    } else return reject(new Error('not a function'));
  });
}
var callback = function(par, callback) {
  var rnd = Math.floor(Math.random() * 2) + 1;
  return rnd > 1 ? callback(null, par) : callback(new Error("trap"));
}

callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))

P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))

La Pfunción requiere que la firma de devolución de llamada debe ser callback(error,result).

loretoparisi
fuente
1
¿Qué ventaja tiene esto sobre promisify nativo o sobre las respuestas anteriores?
Benjamin Gruenbaum
¿A qué te refieres con promisify nativo?
loretoparisi
util.promisify(fn)
Benjamin Gruenbaum
ah si claro :) Justo y ejemplo para mostrar la idea básica. De hecho, puede ver cómo incluso el nativo requiere que la firma de la función deba definirse como algo (err, value) => ...o usted debe definir una personalizada (consulte Funciones personalizadas personalizadas). Gracias buena catcha.
loretoparisi
1
@loretoparisi FYI, var P = function (fn, ...args) { return new Promise((resolve, reject) => fn.call(this, ...args, (error, result) => error ? reject(error) : resolve(result))); };haría lo mismo que el tuyo y es mucho más simple.
Patrick Roberts
1

A continuación se muestra la implementación de cómo una función (API de devolución de llamada) se puede convertir en una promesa.

function promisify(functionToExec) {
  return function() {
    var array = Object.values(arguments);
    return new Promise((resolve, reject) => {
      array.push(resolve)
      try {
         functionToExec.apply(null, array);
      } catch (error) {
         reject(error)
      }
    })
  }
}

// USE SCENARIO

function apiFunction (path, callback) { // Not a promise
  // Logic
}

var promisedFunction = promisify(apiFunction);

promisedFunction('path').then(()=>{
  // Receive the result here (callback)
})

// Or use it with await like this
let result = await promisedFunction('path');
Mzndako
fuente
-2

Es como 5 años tarde, pero quería publicar aquí mi versión promesify que toma funciones de API de devoluciones de llamada y las convierte en promesas

const promesify = fn => {
  return (...params) => ({
    then: cbThen => ({
      catch: cbCatch => {
        fn(...params, cbThen, cbCatch);
      }
    })
  });
};

Eche un vistazo a esta versión muy simple aquí: https://gist.github.com/jdtorregrosas/aeee96dd07558a5d18db1ff02f31e21a

Julian Torregrosa
fuente
1
Eso no es una promesa, no encadena, trata los errores arrojados en la devolución de llamada o acepta un segundo parámetro en ese entonces ...
Benjamin Gruenbaum