Comment convertir une API de rappel existante en promesses?

Mar 20 2014

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

1. Chargement DOM ou autre événement ponctuel:

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

};

2. Rappel simple:

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

3. Rappel de style de nœud ("nodeback"):

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

4. Une bibliothèque entière avec des rappels de style nœud:

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

Comment travailler avec l'API dans les promesses, comment la «promettre»?

Réponses

769 BenjaminGruenbaum Mar 20 2014 at 05:47

Les promesses ont un état, elles commencent comme en attente et peuvent se résumer à:

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

Les fonctions de retour de promesse ne devraient jamais lancer , elles devraient renvoyer des rejets à la place. Lancer à partir d'une fonction de retour de promesse vous obligera à utiliser à la fois a } catch { et a .catch. Les personnes utilisant des API promises ne s'attendent pas à ce que des promesses soient lancées. Si vous n'êtes pas sûr du fonctionnement des API asynchrones dans JS, veuillez d'abord lire cette réponse .

1. Chargement DOM ou autre événement ponctuel:

Ainsi, créer des promesses signifie généralement spécifier quand elles se règlent, c'est-à-dire quand elles passent à la phase remplie ou rejetée pour indiquer que les données sont disponibles (et accessibles avec .then).

Avec des implémentations de promesse modernes qui prennent en charge le Promiseconstructeur comme les 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 prennent en charge le différé (utilisons $ q pour cet exemple ici, mais nous utiliserons également jQuery plus tard):

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

Ou avec un jQuery comme l'API, accrocher un événement qui se produit une fois:

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

2. Rappel simple:

Ces API sont plutôt courantes puisque bien… les callbacks sont courants dans JS. Regardons le cas courant d'avoir onSuccesset onFail:

function getUserData(userId, onLoad, onFail) { …

Avec des implémentations de promesse modernes qui prennent en charge le Promiseconstructeur comme les promesses natives ES6:

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

Avec les bibliothèques qui prennent en charge le 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 propose également un $.Deferred(fn)formulaire, qui a l'avantage de nous permettre d'écrire une expression qui émule de très près le new Promise(fn)formulaire, comme suit:

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

Remarque: Ici, nous exploitons le fait qu'un jQuery différé resolveet les rejectméthodes sont "détachables"; c'est à dire. ils sont liés à l' instance d'un jQuery.Deferred (). Toutes les bibliothèques n'offrent pas cette fonctionnalité.

3. Rappel de style de nœud ("nodeback"):

Les rappels de style nœud (nodebacks) ont un format particulier où les callbacks sont toujours le dernier argument et son premier paramètre est une erreur. Commençons par en promettre 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 les différés, vous pouvez faire ce qui suit (utilisons Q pour cet exemple, bien que Q prenne désormais en charge 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 devriez pas trop promettre les choses manuellement, la plupart des bibliothèques de promesses conçues avec Node à l'esprit ainsi que les promesses natives dans Node 8+ ont une méthode intégrée pour promettre les nœuds. Par exemple

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

4. Une bibliothèque entière avec des rappels de style nœud:

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

Promise.promisifyAll(API);

Ou avec des promesses natives dans 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}), {});

Remarques:

  • Bien sûr, lorsque vous êtes dans un .thengestionnaire, vous n'avez pas besoin de promettre des choses. Le retour d'une promesse d'un .thengestionnaire résoudra ou rejettera avec la valeur de cette promesse. Lancer d'un .thenmanieur est également une bonne pratique et rejettera la promesse - c'est la fameuse promesse de sécurité des lancers.
  • Dans un onloadcas réel , vous devriez utiliser addEventListenerplutôt que onX.
58 efkan Jan 02 2017 at 20:19

Aujourd'hui, je peux utiliser Promisein Node.jscomme une simple méthode Javascript.

Un exemple simple et basique pour Promise(avec KISS way):

Plaine Javascript Async Code API:

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

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

    successCallback( number / divider )

}

Promise Code de l'API Javascript Async:

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 de visiter cette belle source )

Aussi Promisepeut être utilisé avec ensemble async\awaitdans ES7pour rendre le déroulement du programme d' attente pour un fullfiledré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

Une autre utilisation avec le même code en utilisant la .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) })

Promisepeut également être utilisé sur n'importe quelle plate-forme basée sur Node.js comme react-native.

Bonus : une méthode hybride
(la méthode de rappel est supposée avoir deux paramètres comme erreur et résultat)

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 )

     })

}

La méthode ci-dessus peut répondre au résultat pour les utilisations de rappel et de promesse à l'ancienne.

J'espère que cela t'aides.

35 SivaKannan Aug 11 2017 at 18:31

Avant de convertir une fonction en tant que promesse dans 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)
})

Après l'avoir converti

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

Dans le cas où vous devez gérer plusieurs demandes

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)
});
23 Leo Jan 14 2015 at 11:15

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

function promiseDOMready() {
    return new Promise(function(resolve) {
        if (document.readyState === "complete") return resolve();
        document.addEventListener("DOMContentLoaded", resolve);
    });
}
promiseDOMready().then(initOnLoad);
15 GianMarco May 31 2017 at 13:46

Node.js 8.0.0 inclut une nouvelle util.promisify()API qui permet aux API de style de rappel Node.js standard d'être enveloppées dans une fonction qui renvoie une promesse. Un exemple d'utilisation de util.promisify()est illustré 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

14 Bruno May 16 2017 at 12:35

Dans la version candidate de Node.js 8.0.0, il existe un nouvel utilitaire util.promisify(j'ai écrit sur util.promisify ), qui encapsule la capacité de promettre n'importe quelle fonction.

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

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

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

Ensuite, vous avez une readFileméthode qui renvoie un natif Promise.

readFile('./notes.txt')
  .then(txt => console.log(txt))
  .catch(...);
7 Apoorv Jun 20 2016 at 20:38

Vous pouvez utiliser des promesses natives JavaScript avec Node JS.

Lien de code 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
7 daviddavis Nov 28 2016 at 10:07

Avec le vieux javaScript vanilla, voici une solution pour promettre un rappel d'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);
});
6 JasonLoveman Apr 08 2015 at 01:30

La bibliothèque Q de kriskowal comprend des fonctions de rappel à la promesse. 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) {
});
4 user1852503 Aug 04 2016 at 07:45

Lorsque vous avez quelques fonctions qui prennent un rappel et que vous voulez qu'elles renvoient une promesse à la place, vous pouvez utiliser cette fonction pour effectuer 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;
    }
}
4 PaulSpaulding Apr 12 2017 at 23:48

Sous le nœud v7.6 + qui a intégré les promesses et l'asynchrone:

// 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 DoAsync Oct 13 2017 at 05:19

Dans Node.js 8, vous pouvez promettre des méthodes d'objet à la volée en utilisant ce module npm:

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

Il utilise util.promisify et Proxies pour que vos objets restent inchangés. La mémorisation se fait également 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 des fonctions:

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

Vous pouvez même utiliser natif callet applylier du contexte:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2 NicolasZozol Jan 22 2017 at 20:22

Vous pouvez utiliser Promise native dans ES6, par exemple en traitant 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, elle reject()n'est donc jamais appelée.

2 jituanlin Jul 30 2017 at 20:39

La fonction de style de rappel est toujours comme celle-ci (presque toutes les fonctions de node.js sont de 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 de rappel 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 avec 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
        )
    )
)

Pour plus de concision, l'exemple ci-dessus a utilisé ramda.js. Ramda.js est une excellente bibliothèque pour la programmation fonctionnelle. Dans le code ci-dessus, nous avons utilisé son application (comme javascript function.prototype.apply) et son ajout (comme javascript function.prototype.push). Nous pourrions donc convertir la fonction de style de rappel en fonction de style de promesse maintenant:

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

La fonction toPromise et checkErr est propre à la bibliothèque berserk , c'est une bibliothèque de programmation fonctionnelle fork de ramda.js (créée par moi).

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

2 onmyway133 Oct 09 2018 at 20:35

Tu peux faire quelque chose comme ça

// @flow

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

export default toPromise

Alors utilisez-le

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

  console.log(friends)
}
1 Pujan Oct 18 2017 at 06:56

es6-promisify convertit les fonctions basées sur les rappels en fonctions basées sur Promise.

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

const promisedFn = promisify(callbackedFn, args);

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

1 loretoparisi Nov 30 2017 at 06:34

Ma version promisify d'une callbackfonction est la Pfonction:

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 Pfonction nécessite que la signature de rappel soit callback(error,result).

1 Mzndako Jun 21 2019 at 17:38

Vous trouverez ci-dessous la mise en œuvre de la façon dont une fonction (API de rappel) peut être convertie en promesse.

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

1 JosiahNyarega Sep 25 2020 at 01:28

Du futur 😄

Une fonction générique simple que j'utilise normalement.

const promisify = (fn, ...args) => {
  return new Promise((resolve, reject) => {
    fn(...args, (err, data) => {
      if (err) {
        return reject(err);
      }
      resolve(data);
    });
  });
};

Comment l'utiliser

   promisify(fn, arg1, arg2)

Vous ne cherchez probablement pas cette réponse, mais cela vous aidera à comprendre le fonctionnement interne des outils disponibles