web-dev-qa-db-fra.com

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 tel que:

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

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

};

2. Rappel normal:

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

3. Rappel de style Node ("nodeback"):

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

4. Une bibliothèque entière avec des rappels de style de noeud:

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

Comment puis-je utiliser l'API dans les promesses, comment la "promettre"?

666
Benjamin Gruenbaum

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

  • rempli 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 doivent jamais lancer , elles doivent plutôt renvoyer les rejets. Lancer depuis une fonction de retour de promesse vous obligera à utiliser à la fois un } catch { et un .catch. Les personnes utilisant des API promises ne s’attendent pas à des promesses. Si vous ne savez pas comment les API asynchrones fonctionnent dans JS, veuillez commencer par voir cette réponse .

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

Donc, créer des promesses signifie généralement spécifier quand elles sont réglées - cela signifie quand elles passent à la phase accomplie ou rejetée pour indiquer que les données sont disponibles (et peuvent être consultées avec .then).

Avec les implémentations modernes de promesse qui supportent le constructeur Promise comme les promesses ES6 natives:

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 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 une API de type jQuery, accrocher un événement se produisant une fois:

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

2. Rappel normal:

Ces API sont plutôt communes car… les rappels sont fréquents dans JS. Regardons le cas courant d'avoir onSuccess et onFail:

function getUserData(userId, onLoad, onFail) { …

Avec les implémentations modernes de promesse qui supportent le constructeur Promise comme les promesses ES6 natives:

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 présente l’avantage de nous permettre d’écrire une expression qui émule de manière très proche la forme new Promise(fn), comme suit:

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

Remarque: Nous exploitons ici le fait que les méthodes resolve et reject d'un jQuery différé sont "détachables"; c'est à dire. ils sont liés à l'occurrence d'un jQuery.Deferred (). Toutes les bibliothèques n'offrent pas cette fonctionnalité.

3. Rappel de style Node ("nodeback"):

Les callbacks de style de nœud (nodebacks) ont un format particulier dans lequel les callbacks sont toujours le dernier argument et le 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 différé, 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, il ne faut pas trop promettre manuellement les choses, la plupart des bibliothèques de promesses conçues avec l’esprit Node à l’esprit, ainsi que les promesses natives de Node 8+ disposent d’une méthode intégrée nœuds arrière. 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 de noeud:

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

Promise.promisifyAll(API);

Ou avec des promesses natives dans Noeud :

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 gestionnaire .then, vous n'avez pas besoin de promettre des choses. Renvoyer une promesse d'un gestionnaire .then sera résolu ou rejeté avec la valeur de cette promesse. Lancer d'un gestionnaire .then est également une bonne pratique et rejettera la promesse - c'est la fameuse sécurité du lancer de promesse.
  • Dans un cas réel onload, vous devez utiliser addEventListener plutôt que onX.
689
Benjamin Gruenbaum

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

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

Plain Code de l'API Async Javascript:

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

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

    successCallback( number / divider )

}

Promise Code de l'API asynchrone Javascript:

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 )

De plus, Promise peut être utilisé avec async\await dans ES7 pour faire en sorte que le flux du programme attende un résultat fullfiled comme celui-ci:

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 méthode .then()

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 toute plate-forme basée sur Node.js telle que react-native.

Bonus: Une méthode hybride
(La méthode de rappel est supposée avoir deux paramètres d'erreur et de 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.

48
efkan

Avant de convertir une fonction comme 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 conversion

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

Si vous avez besoin de gérer plusieurs requêtes

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

Je ne pense pas que la suggestion de window.onload de @Benjamin fonctionne tout le temps, car elle ne détecte pas si elle est appelée après le chargement. Cela m'a souvent mordu. 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);
21
Leo

Node.js 8.0.0 inclut une nouvelle API util.promisify() qui permet aux API de style de rappel Node.js standard d'être encapsulées dans une fonction qui renvoie une promesse. Un exemple d'utilisation de util.promisify() est présenté ci-dessous.

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

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

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

Voir Support amélioré pour Promises

12

Dans la version candidate de Node.js 8.0.0, il existe un nouvel utilitaire, util.promisify (je viens d’écrire à propos de til.promisify ), qui encapsule la capacité de promettre une fonction quelconque.

Ce 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 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 méthode readFile qui retourne un Promise natif.

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

Vous pouvez utiliser les promesses natives JavaScript avec Node JS.

Lien vers le 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
5
Apoorv

La bibliothèque Q de kriskowal comprend des fonctions de rappel sur 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
Jason Loveman

Sous le noeud v7.6 + qui a des promesses intégrées 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
Paul Spaulding

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

Dans Node.js 8, vous pouvez promisifier des méthodes d'objet à la volée à l'aide de ce module npm:

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

Il utilise util.promisify et Proxies afin que vos objets restent inchangés. La mémorisation est également réalisée 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 natif call et apply pour lier certains contextes:

doAsync(myFunc).apply(context, params)
  .then(result => { /*...*/ });
2
Do Async

Lorsque vous avez quelques fonctions qui prennent un rappel et que vous voulez qu’elles renvoient une promesse, 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;
    }
}
2
user1852503

Le style de rappel a toujours la même fonction (presque toutes les fonctions de node.js correspondent à 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 pourriez é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 est utilisé ramda.js. Ramda.js est une excellente bibliothèque pour la programmation fonctionnelle. Dans le code ci-dessus, nous avons utilisé et appliquer (comme javascript function.prototype.apply) et append (comme javascript function.prototype.Push). Nous pourrions donc convertir la fonction de style callback en fonction de style promesse maintenant:

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

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

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

1
jituanlin

Vous pouvez utiliser Promise native dans ES6, par exemple pour 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
Nicolas Zozol

Vous pouvez 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 utilise-le

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

  console.log(friends)
}
1
onmyway133

Ma version promisify d'une fonction callback est la fonction P:

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

0
loretoparisi

Ci-dessous, vous trouverez comment une fonction (API de rappel) peut être convertie en une 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');

0
Mzndako

es6-promisify convertit les fonctions basées sur le rappel 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

0
Pujan Srivastava