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"?
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 utiliseraddEventListener
plutôt queonX
.
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.
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)
});
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);
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(...);
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
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) => { /** ... **/ });
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) {
});
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;
}
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;
}
}
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);
});
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 => { /*...*/ });
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é.
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:
-
la fonction callback 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 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.
es6-promisify
convertit les fonctions de rappel en fonctions basées sur des promesses.
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
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)
.
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)
}
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));