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. 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
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 Promise
constructeur 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 onSuccess
et onFail
:
function getUserData(userId, onLoad, onFail) { …
Avec des implémentations de promesse modernes qui prennent en charge le Promise
constructeur 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é resolve
et les reject
mé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
.then
gestionnaire, vous n'avez pas besoin de promettre des choses. Le retour d'une promesse d'un.then
gestionnaire résoudra ou rejettera avec la valeur de cette promesse. Lancer d'un.then
manieur est également une bonne pratique et rejettera la promesse - c'est la fameuse promesse de sécurité des lancers. - Dans un
onload
cas réel , vous devriez utiliseraddEventListener
plutôt queonX
.
Aujourd'hui, je peux utiliser Promise
in Node.js
comme 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 Promise
peut être utilisé avec ensemble async\await
dans ES7
pour rendre le déroulement du programme d' attente pour 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
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) })
Promise
peut é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.
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)
});
Je ne pense pas que la window.onload
suggestion 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);
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) => { /** ... **/ });
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 readFile
méthode qui renvoie un natif Promise
.
readFile('./notes.txt')
.then(txt => console.log(txt))
.catch(...);
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
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);
});
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) {
});
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;
}
}
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;
}
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 call
et apply
lier du contexte:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
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.
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:
la fonction de rappel est passée par le dernier argument.
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.
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)
}
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);
Ma version promisify d'une callback
fonction est la P
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 P
fonction nécessite que la signature de rappel soit callback(error,result)
.
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');
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