Comment transformer un rappel nested en promesse?

Récemment, j’ai commencé à utiliser pg-promise avec la bibliothèque bluebird. J’ai toujours nested callback et manipulé des err dans chaque callback. Je trouve que la déclaration de catch promis semble vraiment soignée. Je ne suis pas sûr s’il est possible de transformer ce code pour promettre base?

 username = username.toUpperCase(); let text = "SELECT * FROM users WHERE username = $1"; let values = [username]; database.one(text, values).then(function (userObject) { // ANY WAY TO TURN this nested bycrypt into promise chain? bcrypt.compare(password, userObject.password, function (err, same) { if (err) { return next(err, null); } if (!same) { return next(new Error("Password mismatched!"), null); } const serializeObject = {_id: userObject._id}; return next(null, serializeObject); }); }).catch(function (err) { return next(err, null); }); 

J’imagine qu’en utilisant bluebirds promisify, vous prometsortingez bcrypt.compare comme ça (vous n’avez pas à utiliser la partie async du nom)

 let compareAsync = Promise.promisify(bcrypt.compare); 

parce que userObject dans le premier fichier .then doit être utilisé dans le second. Ensuite, vous ne pouvez pas simplement chaîner les fichiers .then en retournant compareAsync, car le prochain .then n’aura pas access à userObject

un correctif, consiste à utiliser une variable qui sera dans la scope des deux .then (mais pouah)

 username = username.toUpperCase(); let text = "SELECT * FROM users WHERE username = $1"; let values = [username]; let uo; //hacky the outer scoped variable database.one(text, values).then(function (userObject) { uo = userObject; return compareAsync(password, userObject.password); }).then(function(same) { if (!same) { throw new Error("Password mismatched!"); } const serializeObject = {_id: uo._id}; return next(null, serializeObject); }).catch(function (err) { return next(err, null); }); 

une autre option (à mon avis plus propre) est un nested .then

 username = username.toUpperCase(); let text = "SELECT * FROM users WHERE username = $1"; let values = [username]; database.one(text, values).then(function (userObject) { return compareAsync(password, userObject.password) // [optional] following three lines to generate a "nicer" error for compare failure .catch(function(err) { throw "bcrypt.compare failed"; }) // nested .then to pass on the userObject and same at the same time .then(function (same) { return { same: same, userObject: userObject }; }); }).then(function (result) { let same = result.same, userObject = result.userObject; if (!same) { throw new Error("Password mismatched!"); } let serializeObject = { _id: userObject._id }; return next(null, serializeObject); }).catch(function (err) { return next(err, null); }); 

REMARQUE: bluebird a une fonction promisifyAll … qui promet des fonctions dans un object et ajoute (par défaut) le suffixe Async au nom de la fonction. Je pense que vous pouvez choisir un nom postfix différent, mais la documentation vous en dira plus.

lorsque vous promettez une seule fonction, vous déclarez le nom vous-même – ce qui précède aurait facilement pu être

 let trumpIsBigly = Promise.promisify(bcrypt.compare); 

alors vous utiliseriez simplement trumpIsBigly où le code a compareAsync

Une dernière possibilité

CompareAsync promisified roulé à la main (principalement tiré de la réponse de vitaly-t mais avec des ajouts)

 function compareAsync(password1, password2, inValue) { return new Promise(function (resolve, reject) { bcrypt.compare(password1, password2, function (err, same) { err = err || (!same && new Error("Password mismatched!")); if (err) { reject(err); } else { resolve(inValue); } }); }); } 

Maintenant, compareAsync ne résoudra la valeur entrante inValue que s’il n’y a pas d’erreur, et que c’est pareil

 username = username.toUpperCase(); let text = "SELECT * FROM users WHERE username = $1"; let values = [username]; database.one(text, values).then(function (userObject) { return compareAsync(password, userObject.password, userObject) }).then(function (userObject) { let serializeObject = { _id: userObject._id }; return next(null, serializeObject); }).catch(function (err) { return next(err, null); }); 

Ce qui rend la “chaîne” très simple!

Ceci va s’étendre à la réponse de @ Jaromanda, au cas où vous n’utiliseriez que cette fonction et que vous souhaitiez simplement voir comment la promulguer manuellement.

 function samePassword(password1, password2) { return new Promise(function (resolve, reject) { bcrypt.compare(password1, password2, (err, same) => { err = err || (!same && new Error("Password mismatched!")); if (err) { reject(err); } else { resolve(); } }); }); } db.one(text, values) .then(userObject => { return samePassword(password, userObject.password); }) .catch(error => { return next(error, null); }); 

Autre que cela, l’approche promisify est la voie à suivre. Mais il est toujours bon de comprendre ce qu’il fait effectivement;)