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


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

1. Carga de DOM u otro evento de una vez:

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

};

2. Llamar de vuelta:

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

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

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

4. Una biblioteca completa con devoluciones de llamadas 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 lo "promulgo"?


529
2018-03-19 22:47


origen


Respuestas:


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ó.

Promesa de funciones de retorno nunca debe tirar, deberían devolver rechazos en su lugar. Lanzar desde una función de devolución de promesa te obligará a usar un } catch {  y un .catch. Las personas que usan API promisificadas no esperan promesas. Si no está seguro de cómo funcionan las API asíncronas en JS, por favor ver esta respuesta primero.

1. Carga de DOM u otro evento de una vez:

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

Con implementaciones prometedoras modernas que apoyan la Promise constructor como promesas ES6 nativas:

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

Entonces usarías la promesa resultante así:

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 una API jQuery like, conectando un evento que ocurre una vez:

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

2. Llamar de vuelta:

Estas API son bastante comunes ya que bien ... las devoluciones de llamadas son comunes en JS. Veamos el caso común de tener onSuccess y onFail:

 function getUserData(userId, onLoad, onFail){ ...

Con implementaciones prometedoras modernas que apoyan la Promise constructor como promesas ES6 nativas:

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 usamos $ q más 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 una $.Deferred(fn) forma, que tiene la ventaja de que nos permite escribir una expresión que emula 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 un jQuery diferido resolve y reject los métodos son "separables"; es decir. están ligados a la ejemplo de un jQuery.Deferred (). No todas las bibliotecas ofrecen esta característica.

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

Las devoluciones de llamada de estilo de nodo (nodebacks) tienen un formato particular donde las devoluciones de llamada son siempre el último argumento y su primer parámetro es un error. Primero promisitemos 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) return reject(err);
             resolve(data);
         });
    });
}

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

function getStuffAsync(param){
    var d = Q.defer();
    getStuff(param,function(err,data){
         if(err !== null) return d.reject(err); // `throw err` also works here.
             d.resolve(data);
    });
    return d.promise;   
}

En general, no debe promisificar las cosas manualmente demasiado, la mayoría de las bibliotecas prometedoras que fueron diseñadas con Nodo en mente, así como las promesas nativas en el Nodo 8+ tienen un método incorporado para promisificar los nudismos. 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 llamadas de estilo de nodo:

Aquí no hay una regla de oro, los promistes uno por uno. Sin embargo, algunas implementaciones prometedoras le permiten hacer esto a granel, por ejemplo, en Bluebird, la conversión de una API de nodo a API de promesa es tan simple como:

Promise.promisifyAll(API);

O con promesas nativas en Nodo:

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

Notas:

  • Por supuesto, cuando estás en un .then controlador no necesita promisificar cosas. Devolviendo una promesa de un .then el manejador resolverá o rechazará con el valor de esa promesa. Lanzando desde un .then el manejador también es una buena práctica y rechazará la promesa: esta es la famosa promesa de seguridad de lanzamiento.
  • En un real onload caso, deberías usar addEventListener más bien que onX.

546
2018-03-19 22:47



Hoy puedo usar Promise en Node.js como un método simple de Javascript.

Un ejemplo simple y básico para Promise (con BESO camino):

Llanura Código Javascript Async API:

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

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

    successCallback( number / divider )

}

Promise Código Javascript Async API:

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 Promise se puede usar con juntos async\await en ES7 para hacer que el flujo del programa espere un fullfiled resultado 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 al usar .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) })

Promise también se puede usar en cualquier plataforma que esté basada en Node.js como react-native.

Espero que esto ayude.


29
2018-01-02 13:19



No creo que el window.onload la sugerencia de @Benjamin funcionará todo el tiempo, ya que no detecta si se llama después de la carga. Me han mordido 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);

18
2018-01-14 04:15



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');
var Promise = require('bluebird');

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

16
2017-08-11 11:31



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 readFile método que devuelve un nativo Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);

7
2018-05-16 05:35



Puede usar promesas nativas de JavaScript con Node JS.

Enlace de 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

5
2018-06-20 13:38



Node.js 8.0.0 incluye un nuevo util.promisify() API que permite que las API de estilo de devolución de llamada estándar de Node.js queden envueltas en una función que devuelva 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 Promises


4
2018-05-31 06:46



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

3
2018-04-07 18:30



Bajo el nodo v7.6 + que tiene promesas incorporadas y asincrónicas:

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

3
2018-04-12 16:48



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

2
2017-08-04 00:45



Con la simple y antigua versión 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);
});

2
2017-11-28 03:07