Promises basics. How to promisify async node code?

僤鯓⒐⒋嵵緔 提交于 2020-01-17 06:12:11

问题


I am getting started with promises and trying to use them instead of callbacks to avoid a callback hell. The async functions are a mix of functions from MongoDB, Redis, bcrypt module, etc. I was able to get this far:

var insert = Q.denodify(db.collection(USERS).insert);
var createCollection = Q.denodify(db.createCollection);
var sadd = Q.denodify(redisClient.sadd);
var sismember = Q.denodify(redisClient.sismember);
var genSalt = Q.denodify(bcrypt.genSalt);
var hash = Q.denodify(bcrypt.hash);

// SANITY CHECK
// a "name" parameter is required
if(!req.body.name || !isValidName(req.body.name)) 
    return next(get400InvalidNameError());

// SANITY CHECK
// a "key" is optional
// if one is supplied, it must be valid
// this key will be hashed later
if(req.body.key){
    if(!isValidKey(req.body.key))
        return next(get400InvalidKeyError());
}

// STEPS:
// 1. 
// check Redis cache to see if the "name" is already taken
// if yes, return error. if no, continue
// 2. 
// construct a "user" object with name = req.body.name
// 3. 
// req.body.key provided?
// if yes, generate salt and hash the key. Set user.key = hash
// if not, continue
// 4.
// create a collection in MongoDB with the same name as req.body.name
// 5.
// add req.body.name to cache
// 6. 
// send a response to user using res.json() / res.end()
sismember(USERS,req.body.name)
.then(createUserObj,errHandler)
.then(genSalt(10),errHandler)
.then(hash(req.body.key,salt))
.then(createCollection(req.body.name),errHandler)
.then(sadd(USERS,req.body.name),errHandler)
.then(insert(user),errHandler)
.then(get200UserCreated,errHandler)

What confuses me is the last part where all of these functions are then()-ed together. I have a few questions:
1. How can the result of one async function be made available to another?
2. How can I conditionally decide which functions get executed? For example, I want to generate salt and hash only when req.body.key is provided.
3. Is my then() sequence proper?


回答1:


  1. How can the result of one async function be made available to another?

when you use Promise you can resolve or reject it.

var myprom = Promise.resolve(1) // the simplest promise way

Here if we chain with a then the argument will be equal to 1.

myprom.then(function( val ){

    console.log(val); // 1
    return 33;

}).then(function(passed){

    console.log(passed) // 33
    return 44;

}).then(function(other){

    console.log(other) // 44
    if( other > 44 ){
        return 'AAA';
    } else {
        return 'BBB';
    }
}).then(function(res){

    console.log(res) // BBB

})

here the important thing is that you return something from your promise.
Now the async. part :

// we can also instanciate a new Promise object
var myprom = new Promise(function(resolve , reject){

   console.log('we start here');

   setTimeout(function(){
       console.log('we continue here');
       resolve( 'time out' );
   } , 2000);

}).then(function( val ){ 
    console.log('and we end here : ' + val);
});

The second part then is invoked by the call of resolve in the first part.
So we always wait until the end, it's the "magic" of promise.

The argument passed to resolve become the argument of the next then.
It is the same as our first example about the return it is very important

When you do :

 sismember(USERS,req.body.name)

it is the same principle that the timeout.

 var sismember = function(USERS , req.body.name){
     var promiseToReturn = new Promise(function(resolve,reject){
         var result = ''; // <---do something with USERS and req.body.name

         // and if it is ok 
           if(result){
            resolve( result )
           } else {// if it is not ok 
            reject( result )
           }

     });
     return promiseToReturn; // <-- we can chain with then
 })
 .then(createUserObj,errHandler) 

The next then (after sismember) will invoke createUserObj with result as argument.
It become :

 var sismember = function(USERS , req.body.name){
     var promiseToReturn = new Promise(function(resolve,reject){
         var result = ''; // <---do something with USERS and req.body.name

         // and if it is ok 
           if(result){
            resolve( result )
           } else {// if it is not ok 
            reject( result )
           }

     });
     return promiseToReturn; // <-- we can chain with then
 })
 .then(function createUserObj( resultOfsismember){
      var createdUser = {} //<--- do what you want with the resultOfsismember
      // and return something to be handled by next then.
     if(everyThingIsOk){ 
       return createdUser;
     } else {
       return Promise.reject( 'error during creation');
       // this will be handled by next then error handler or catch
     }
 } , errHandler ); //<--- you provide here an error handler for  sismember
  1. How can I conditionally decide which functions get executed? For example, I want to generate salt and hash only when req.body.key is provided.

Different way to do that.

  1. Is my then() sequence proper?

No !

you can't call a function like you do.

.then(genSalt(10),errHandler) // <-- you are invoking the result not the function
// you are doing something like that:
.then('Adekj34LLKDF' , errHandler)

if you want to add an argument use the bind

 .then(genSalt.bind(null , 10), errHandler)
 // we invoke the genSalt in a null context with 10 as first argument

But the promise chain provide the argument from the prev function you don't have to provide it !
see example above


once you resolved this problem, add a catch at the end to handle any error that could occur. this part :

.then(get200UserCreated,errHandler)//<--- errHandler for prev then

have an error handler but only for this part :

.then(insert(user),errHandler)

if you have an error during insert(user) it will be handled by the next then error handler or catch.

sismember(USERS,req.body.name)
.then(createUserObj,errHandler)// <-- errHandler for : sismember
.then(genSalt(10),errHandler)// <-- errHandler for : createUserObj
.then(hash(req.body.key,salt))// <-- missing errHandler for : genSalt
.then(createCollection(req.body.name),errHandler)// <-- errHandler for : hash
.then(sadd(USERS,req.body.name),errHandler)// <-- errHandler for createCollection
.then(insert(user),errHandler)// <-- errHandler for : 
.then(get200UserCreated,errHandler)// <-- errHandler for : insert
.catch(errHandler)// <-- errHandler for : get200UserCreated 

i've wrote something about error handler here, you should take a look at that.



来源:https://stackoverflow.com/questions/33051270/promises-basics-how-to-promisify-async-node-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!