问题
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:
- 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
- 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.
- 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