承诺基础知识。如何承诺异步节点代码?
Promises basics. How to promisify async node code?
我开始使用 promises 并尝试使用它们而不是回调来避免回调地狱。异步函数是 MongoDB、Redis、bcrypt 模块等函数的混合。我能做到这一点:
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)
让我感到困惑的是最后一部分,所有这些功能都 then()
-ed 在一起。我有几个问题:
1. 如何让一个异步函数的结果对另一个可用?
2. 如何有条件地决定执行哪些函数?例如,我只想在提供 req.body.key
时生成 salt 和 hash。
3. 我的then()
顺序对吗?
- How can the result of one async function be made available to another?
当您使用 Promise 时,您可以解决或拒绝它。
var myprom = Promise.resolve(1) // the simplest promise way
在这里,如果我们用 then
链接,参数将等于 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
})
这里重要的事情是你return
你的承诺。
现在异步。部分:
// 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);
});
第二部分then
由第一部分resolve
的调用调用。
所以我们总是等到最后,这就是承诺的“魔力”。
传递给resolve的参数成为下一个then
的参数。
它与我们关于 return
的第一个示例相同,它非常 重要
当你这样做时:
sismember(USERS,req.body.name)
超时是同一个原理
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)
下一个 then(在 sismember 之后)将以 result
作为参数调用 createUserObj
。
它变成:
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.
不同的方法。
- Is my then() sequence proper?
没有!
你不能像现在这样调用函数。
.then(genSalt(10),errHandler) // <-- you are invoking the result not the function
// you are doing something like that:
.then('Adekj34LLKDF' , errHandler)
如果要添加参数,请使用 bind
.then(genSalt.bind(null , 10), errHandler)
// we invoke the genSalt in a null context with 10 as first argument
但是 promise 链 提供来自 prev 函数的参数,您不必提供它!
见上例
解决此问题后,在末尾添加一个 catch 以处理可能发生的任何错误。
这部分:
.then(get200UserCreated,errHandler)//<--- errHandler for prev then
有一个错误处理程序,但仅限于此部分:
.then(insert(user),errHandler)
如果您在 insert(user)
期间遇到错误,它将由下一个错误处理程序或 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
我写了一些关于错误处理程序的文章 ,你应该看一看。
我开始使用 promises 并尝试使用它们而不是回调来避免回调地狱。异步函数是 MongoDB、Redis、bcrypt 模块等函数的混合。我能做到这一点:
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)
让我感到困惑的是最后一部分,所有这些功能都 then()
-ed 在一起。我有几个问题:
1. 如何让一个异步函数的结果对另一个可用?
2. 如何有条件地决定执行哪些函数?例如,我只想在提供 req.body.key
时生成 salt 和 hash。
3. 我的then()
顺序对吗?
- How can the result of one async function be made available to another?
当您使用 Promise 时,您可以解决或拒绝它。
var myprom = Promise.resolve(1) // the simplest promise way
在这里,如果我们用 then
链接,参数将等于 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
})
这里重要的事情是你return
你的承诺。
现在异步。部分:
// 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);
});
第二部分then
由第一部分resolve
的调用调用。
所以我们总是等到最后,这就是承诺的“魔力”。
传递给resolve的参数成为下一个then
的参数。
它与我们关于 return
的第一个示例相同,它非常 重要
当你这样做时:
sismember(USERS,req.body.name)
超时是同一个原理
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)
下一个 then(在 sismember 之后)将以 result
作为参数调用 createUserObj
。
它变成:
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.
不同的方法。
- Is my then() sequence proper?
没有!
你不能像现在这样调用函数。
.then(genSalt(10),errHandler) // <-- you are invoking the result not the function
// you are doing something like that:
.then('Adekj34LLKDF' , errHandler)
如果要添加参数,请使用 bind
.then(genSalt.bind(null , 10), errHandler)
// we invoke the genSalt in a null context with 10 as first argument
但是 promise 链 提供来自 prev 函数的参数,您不必提供它!
见上例
解决此问题后,在末尾添加一个 catch 以处理可能发生的任何错误。 这部分:
.then(get200UserCreated,errHandler)//<--- errHandler for prev then
有一个错误处理程序,但仅限于此部分:
.then(insert(user),errHandler)
如果您在 insert(user)
期间遇到错误,它将由下一个错误处理程序或 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
我写了一些关于错误处理程序的文章