Comment puis-je 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. Chargement DOM ou autre événement unique:

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

2. Rappel simple:

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

3. Renvoi 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 de nœud:

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

Comment puis-je travailler avec l'API dans les promesses, comment le "promettre"?

Les promesses ont un état, elles commencent comme pendantes et peuvent s'installer:

  • Rempli signifiant que le calcul a été réalisé avec succès.
  • Rejeté signifiant que le calcul a échoué.

Les fonctions de retour de promesse ne devraient jamais lancer , elles devraient renvoyer les rejets à la place. Lancer une fonction de retour de la promesse vous obligera à utiliser a } catch { et a .catch . Les personnes qui utilisent des API promis ne s'attendent pas à des promesses. Si vous ne savez pas très bien comment les API asynchrones fonctionnent dans JS – veuillez voir cette réponse en premier.

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

Ainsi, créer des promesses signifie généralement spécifier quand elles s'installent, c'est-à-dire lorsqu'elles passent à la phase remplie ou rejetée pour indiquer que les données sont disponibles (et peuvent être consultées avec .then ).

Avec des implémentations de promesses modernes qui prennent en charge le constructeur Promise comme promesses ES6 natives:

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

Vous utiliserez alors la promesse qui en résulte:

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

Avec les bibliothèques qui supportent le report (utilisons $ q pour cet exemple ici, mais nous utiliserons plus jQuery plus tard):

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

Ou avec une API jQuery like, accrochant un événement qui se passe une fois:

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

2. Rappel simple:

Ces API sont plutôt communes car bien … les rappels sont courants dans JS. Examinons le cas commun d'avoir onSuccess et onFail :

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

Avec des implémentations de promesses modernes qui prennent en charge le constructeur Promise comme promesses ES6 natives:

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

Avec les bibliothèques qui supportent le report (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(); } 

$.Deferred(fn) offre également une forme $.Deferred(fn) , qui a l'avantage de nous permettre d'écrire une expression qui émule très étroitement la 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 de resolve et de reject jQuery ont différé sont "détachables"; c'est à dire. Ils sont liés à l' instance d'un jQuery.Deferred (). Toutes les libs ne proposent pas cette fonctionnalité.

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

Les retours de style de nœud (nodebacks) ont un format particulier où les rappels sont toujours le dernier argument et son premier paramètre est une erreur. Prenons l'une d'abord manuellement:

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

À:

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

Avec différé, vous pouvez faire ce qui suit (utilisez Q pour cet exemple, bien que Q soit maintenant compatible avec la nouvelle syntaxe que vous préférez):

 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 général, vous ne devez pas promettre trop manuellement les choses, les bibliothèques les plus prometteuses conçues avec Node à l'esprit ainsi que les promesses natives dans le noeud 8+ ont une méthode intégrée pour promettre les dérivations. 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 nœud:

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

 Promise.promisifyAll(API); 

Ou avec des promesses natives dans Node:

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

Remarques:

  • Bien sûr, lorsque vous êtes dans un .then gestionnaire, vous n'avez pas besoin de promettre des choses. Renvoyer une promesse d'un gestionnaire .then va résoudre ou rejeter avec la valeur de cette promesse. Lancer à partir d'un .then manipulateur est également une bonne pratique et rejettera la promesse – c'est la promesse célèbre de lancer la sécurité.
  • Dans un cas de onX actuel, vous devez utiliser addEventListener plutôt que onX .

Je ne pense pas que la suggestion window.onload par @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 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); 

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

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

Simple code Javascript API Async:

 function divisionAPI (number, divider, successCallback, errorCallback) { if (divider == 0) { return errorCallback( new Error("Division by zero") ) } successCallback( number / divider ) } 

Promise Code API Javascript Async:

 function divisionAPI (number, divider) { return new Promise(function (fullfilled, rejected) { if (divider == 0) { return rejected( new Error("Division by zero") ) } fullfilled( number / divider ) }) } 

(Je recommande de visiter cette belle source )

Aussi Promise peut être utilisé avec ensemble async\await dans ES7 pour que le flux du programme attend un résultat fullfiled comme le suivant:

 function getName () { return new Promise(function (fullfilled, rejected) { var name = "John Doe"; // wait 3000 milliseconds before calling fulfilled() method setTimeout ( function() { fullfilled( name ) }, 3000 ) }) } async function foo () { var name = await getName(); // awaits for a fullfilled 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 (fullfilled, rejected) { var name = "John Doe"; // wait 3000 milliseconds before calling fulfilled() method setTimeout ( function() { fullfilled( name ) }, 3000 ) }) } // the console writes "John Doe" after 3000 milliseconds getName().then(function(name){ console.log(name) }) 

J'espère que cela t'aides.

Vous pouvez utiliser les 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 

La bibliothèque Q par kriskowal comprend des fonctions de rappel à 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) { }); 

En version candidate pour 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.

Ce n'est pas très différent 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 méthode readFile qui renvoie une Promise native.

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

Lorsque vous avez quelques fonctions qui nécessitent un rappel et que vous souhaitez qu'elles vous promettent, 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; } } 

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

Sous le nœud v7.6 + qui a intégré des promesses et des astuces:

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

Vous pouvez utiliser Promise natif 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 pas de raison d'échouer, donc reject() n'est jamais appelé.

Avant de convertir une fonction comme promesse Dans Node.JS

 var request = require('request'); //http wrapped module function reqeustWrapper(url, callback) { request.get(url, function (err, response) { if (err) { callback(err); }else{ callback(response); } }) } requestWrapper(url,function(response){ console.log(response) }) 

Après la conversion

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

Incase vous devez gérer la demande multiple

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

Node.js 8.0.0 comprend une nouvelle API util.promisify() 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 d' 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 Amélioration du soutien aux Promesses

La fonction de style de rappel est toujours comme ceci (presque toute la fonction dans node.js est ce style):

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

Ce style a la même fonctionnalité:

  1. La fonction de rappel est transmise par le dernier argument.

  2. La fonction de rappel accepte toujours l'objet d'erreur car c'est le premier argument.

Ainsi, 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 concis, 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 l'avons utilisé, c'est appliquer (comme javascript function.prototype.apply ) et ajouter (comme javascript function.prototype.push ). Donc, nous pourrions convertir la fonction de style rappel pour promettre une fonction de style maintenant:

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

ToPromise et checkErr est propre par une bibliothèque berserk , c'est une fourchette de bibliothèque de programmation fonctionnelle par ramda.js (create by me).

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

Vous pouvez utiliser le pack callback2Promise npm pour convertir les fonctions de style nœud 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));