Comment convertir une API de rappel existante en promesses?

je veux travailler avec des promesses mais j'ai une API de rappel dans un format comme:

1. DOM de la charge ou l'autre moment de l'événement:

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

};

2. Plaine de rappel:

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

3. "Nodeback" ("nodeback"):

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

4. Une bibliothèque complète avec des callbacks de style node:

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

comment travailler avec L'API dans promises, comment le "promisifier"?

557
demandé sur onmyway133 2014-03-20 02:47:26

18 réponses

les promesses ont état, elles commencent comme en attente et peuvent se régler à:

  • rempli ce qui signifie que le calcul s'est terminé avec succès.
  • rejeté ce qui signifie que le calcul a échoué.

Promise returning functions ne devrait jamais jeter , ils devraient retourner les rejets à la place. Lancer d'une fonction de retour de promesse vous forcera à utiliser à la fois un } catch { et a .catch . Les personnes utilisant des API promisifiées ne s'attendent pas à des promesses à jeter. Si vous n'êtes pas sûr comment fonctionnent les API asynchrones dans JS - s'il vous plaît voir cette réponse d'abord.

1. DOM de la charge ou l'autre moment de l'événement:

ainsi, créer des promesses signifie généralement spécifier quand ils se règlent - ce qui signifie Quand ils se déplacent à la phase remplie ou rejetée pour indiquer les données est disponible (et peut être consultée avec .then ).

avec des implémentations promises modernes qui supportent le constructeur Promise comme des promesses natives ES6:

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

vous utiliseriez alors la promesse résultante comme ceci:

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

avec les bibliothèques qui supportent le différé (utilisons $q pour cet exemple ici, mais nous utiliserons aussi jQuery plus tard):

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

ou avec un jQuery comme API, accrochage sur un événement se produisant une fois:

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

2. Plaine de rappel:

ces API sont assez courantes puisque ... les callbacks sont fréquents dans JS. Regardons le cas commun d'avoir onSuccess et onFail :

function getUserData(userId, onLoad, onFail) { …

avec des implémentations promises modernes qui supportent le constructeur Promise comme des promesses natives ES6:

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

avec les bibliothèques qui supportent différé (utilisons jQuery pour cet exemple ici, mais nous avons également utilisé $q ci-dessus):

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

jQuery offre également une forme $.Deferred(fn) , qui a l'avantage de nous permettre d'écrire une expression qui émule très étroitement la forme new Promise(fn) , comme suit:

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

Note: ici nous exploitons le fait que jQuery a reporté resolve et reject " les méthodes sont "détachable", c'est à dire. ils sont liés à la instance d'un jQuery.Différer.)( Ce ne sont pas tous les fichiers Liv qui offrent cette fonctionnalité.

3. "Nodeback":

les callbacks de style de Noeud (nodebacks) ont un format particulier où les callbacks sont toujours le dernier argument et son premier paramètre est une erreur. Nous allons d'abord promisifier un manuellement:

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

à:

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

avec deferreds vous pouvez faire ce qui suit (utilisons Q pour cet exemple, bien que Q supporte maintenant la nouvelle syntaxe que vous devriez préférer ):

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 général, vous ne devez pas promisifier les choses manuellement trop, la plupart des bibliothèques de promesses qui ont été conçues avec le noeud à l'esprit ainsi que les promesses natives dans le noeud 8+ ont une méthode intégrée pour promisifier nodebacks. Par exemple

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

4. Un toute la bibliothèque avec des callbacks de style noeud:

il n'y a pas de règle d'or ici, vous les promisifiez un par un. Cependant, certaines implémentations de promesse vous permettent de le faire en vrac, par exemple dans Bluebird, convertir une API nodeback en API promise est aussi simple que:

Promise.promisifyAll(API);

Ou avec native promesses dans Nœud :

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

Notes:

  • bien sûr, quand vous êtes dans un .then handler vous n'avez pas besoin de promisifier les choses. Retourner une promesse d'un handler .then résoudra ou rejettera avec la valeur de cette promesse. Lancer à partir d'un handler .then est également une bonne pratique et rejettera la promesse - il s'agit de la célèbre sécurité de lancer promesse.
  • dans un cas réel onload , vous devez utiliser addEventListener plutôt que onX .
576
répondu Benjamin Gruenbaum 2018-09-20 13:41:56

Aujourd'hui, je peux utiliser Promise dans Node.js comme une méthode Javascript simple.

exemple simple et basique de Promise (avec KISS way):

simple Javascript code API Async:

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

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

    successCallback( number / divider )

}

Promise Javascript async code API:

function divisionAPI (number, divider) {

    return new Promise(function (fulfilled, rejected) {

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

        fulfilled( number / divider )

     })

}

(je recommande visite cette belle source )

aussi Promise peut être utilisé avec async\await dans ES7 pour faire le flux du programme attendre un fullfiled résultat comme suit:

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

un autre usage avec le même code en utilisant .then() méthode

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 peut également être utilisé sur n'importe quelle plate-forme qui est basée sur un noeud.js comme react-native .

Espérons que cette aide.

33
répondu efkan 2018-03-10 07:23:17

avant de convertir une fonction en promesse dans le noeud.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)
})

Après Conversion

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

au Cas où vous avez besoin de gérer de multiples demande

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)
});
21
répondu Sivashanmugam Kannan 2018-06-12 21:34:27

Je ne pense pas que la suggestion window.onload de @Benjamin fonctionnera tout le temps, car elle ne détecte pas si elle est appelée après la charge. J'ai été mordu par de nombreuses fois. Voici une version qui devrait toujours:

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
18
répondu Leo 2015-03-03 12:32:12

dans release candidate for Node.js 8.0.0, il y a un nouvel utilitaire, util.promisify (j'ai écrit à propos de util.promisify ), qui résume la capacité de promisifier quelque fonction que ce soit.

il n'est pas très différent des approches suggérées dans les autres réponses, mais il a l'avantage d'être une méthode de base, et ne nécessite pas de dépendances supplémentaires.

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

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

alors vous avez une méthode readFile qui retourne un natif Promise .

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
10
répondu Bruno 2017-05-16 05:35:57

vous pouvez utiliser des promesses natives JavaScript avec Node Js.

mon lien de code 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
répondu Apoorv 2016-06-20 14:35:27

Node.js 8.0.0 inclut une nouvelle API util.promisify() qui permet le noeud standard.APIs de style callback js à envelopper dans une fonction qui renvoie une promesse. Un exemple d'utilisation de util.promisify() est montré ci-dessous.

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

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

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

Voir prise en charge Améliorée des Promesses

5
répondu Gian Marco Gherardi 2017-05-31 06:46:50

la bibliothèque Q de kriskowal inclut des fonctions de callback-to-promise. Une méthode comme celle-ci:

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

peut être converti avec Q. ninvoke

Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
3
répondu Jason Loveman 2015-04-07 18:30:14

sous le noeud v7.6 + qui a construit dans les promesses et async:

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

comment utiliser:

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
répondu Paul Spaulding 2017-04-12 16:48:09

lorsque vous avez quelques fonctions qui prennent un rappel et que vous voulez qu'elles retournent une promesse à la place, vous pouvez utiliser cette fonction pour faire la conversion.

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
répondu user1852503 2016-08-25 00:54:47

avec du javaScript classique, voici une solution pour promisifier un rappel 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
répondu daviddavis 2016-11-29 14:44:49

Dans Le Noeud.js 8 Vous pouvez promisify object methods on the fly en utilisant ce module npm:

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

Il utilise des util.promisify et mandataires afin que vos objets restent inchangés. Memoization se fait aussi avec l'utilisation de WeakMaps). Voici quelques exemples:

avec des objets:

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

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

avec fonctions:

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

vous pouvez même utiliser call et apply pour lier un contexte:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2
répondu Do Async 2017-10-12 22:19:07

Vous pouvez utiliser native Promesse dans l'ES6, par exemple de traiter avec 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
    });

}

dans cet exemple, la promesse n'a aucune raison d'échouer, donc reject() n'est jamais appelé.

1
répondu Nicolas Zozol 2017-01-27 13:00:54

Le rappel style "151970920 de la fonction" toujours comme ça(presque toutes les fonctions dans le nœud.js est ce style):

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

ce style a la même caractéristique:

  1. la fonction callback est passée par le dernier argument.

  2. la fonction de rappel accepte toujours l'objet d'erreur comme premier argument.

Donc, vous pouvez écrire une fonction pour convertir une fonction de ce style comme ceci:

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

plus concis, ci-dessus à titre d'exemple ramda.js. Ramda.js est une excellente bibliothèque pour la programmation fonctionnelle. Dans le code ci-dessus, nous avons utilisé appliquer (comme javascript function.prototype.apply ) et ajouter(comme javascript function.prototype.push ). Ainsi, nous pourrions convertir la fonction a callback style pour promettre la fonction style maintenant:

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

toPromise et checkErr fonction est propre par berserk bibliothèque, c'est une bibliothèque de programmation fonctionnelle fourche par ramda.js (créer par moi).

J'espère que cette réponse vous sera utile.

0
répondu JITuan LIn 2017-07-30 13:46:20

es6-promisify convertit les fonctions de rappel en fonctions basées sur des promesses.

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

const promisedFn = promisify(callbackedFn, args);

Réf: https://www.npmjs.com/package/es6-promisify

0
répondu Pujan Srivastava 2017-10-17 23:56:27

Mon promisify version d'un callback de la fonction est la P de la fonction:

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 fonction P exige que la signature de rappel soit callback(error,result) .

0
répondu loretoparisi 2017-11-29 23:34:06

Vous pouvez faire quelque chose comme cela

// @flow

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

export default toPromise

puis l'utiliser

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

  console.log(friends)
}
0
répondu onmyway133 2018-10-09 13:35:18

vous pouvez utiliser le paquet callback2Promise npm pour convertir les fonctions de style de noeud en promesses.

var c2p = require('callback2promise');

// ordinary function with any number of parameters and a callback at the end 
var nodeStyleFunc = function(param1, param2, callback){
  setTimeout(
    function(){ 
    callback(null, 'done') 
  }, 200);
}

// convert the function to a promise 
var promise = c2p(nodeStyleFunc)(param1, param2);

promise
  .then(result => console.log(result))
  .catch(err => console.log(err));
-1
répondu kutlu 2016-09-28 13:45:50