Je veux travailler avec des promesses, mais j'ai une API de rappel dans un format tel que:
window.onload; // set to callback
...
window.onload = function() {
};
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
Les promesses ont un état, elles commencent comme en attente et peuvent se régler comme suit:
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 .
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();
}
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é.
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
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:
.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.onload
, vous devez utiliser addEventListener
plutôt que onX
.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.
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)
});
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);
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) => { /** ... **/ });
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(...);
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
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) {
});
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;
}
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);
});
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 => { /*...*/ });
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;
}
}
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:
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 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.
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é.
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)
}
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)
.
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');
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);