我如何重构瀑布方法,从异步到使用ES6承诺?
How could I refactor the waterfall method from async to use ES6 promises?
我有一个路由,允许用户通过发送电子邮件重置他们的密码。大多数网站的标准程序。在这条路由中,我导入了async npm模块,并使用瀑布方法,这样我就可以处理多个函数的异步特性。我仍然很难理解承诺,但我正在尝试用承诺或承诺链取代瀑布。
我如何用承诺重构这个路由?以下是这条路线中包含的步骤,目前分为4个瀑布功能。
- 首先路由创建一个重置令牌
- 根据email搜索用户2.5. 如果找到用户,保存用户,否则返回404
- 向用户发送包含重置url的邮件
-
返回状态200
app.post('/forgotPassword', function(req, res, next) { waterfall([ // generate reset token function(done) { crypto.randomBytes(20, function(err, buf) { var token = buf.toString('hex'); done(err, token); }); }, function(token, done) { // search for user with the given email User.findOne({ email: req.body.email }, function(err, user) { // check to see if the user exists if (!user) { // user doesn't exist in database return res.status(404).send(); } // user exists, assign token with expiration date user.resetPasswordToken = token; user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now // save the user model with the newly added // token and expiration date user.save(function(err) { done(err, token, user); }); }); }, function(token, user, done) { var smtpTransport = nodemailer.createTransport('SMTP', { service: 'SendGrid', auth: { user: config.sendgridUser, pass: config.sendgridPassword } }); var mailOptions = { to: user.email, from: 'email@school.edu', subject: 'Password Reset', text: `Hello etc etc`, smtpTransport.sendMail(mailOptions, function(err) { done(err, 'done'); }); }], function(err) { // handle error if (err) return next(err); res.status(200).send(); }); }); // end POST route '/forgotPassword'
Promise
是一个非常强大的工具。一开始可能很难理解,但完全值得努力!如果你有任何疑问,请告诉我:)
app.post('/forgotPassword', function(req, res, next)
{
new Promise((resolve, reject) =>
{
// generate reset token
crypto.randomBytes(20, (err, buf) =>
{
if(err)
return reject(err);
const token = buf.toString('hex');
resolve(token);
});
})
.then((token) =>
{
return new Promise((resolve, reject) => {
// search for user with the given email
User.findOne({ email: req.body.email }, (err, user) =>
{
if (!user)
return reject(404);
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
if(err)
return reject(err);
resolve(user);
});
});
});
})
.then((user) =>
{
return new Promise((resolve, reject) =>
{
const smtpTransport = nodemailer.createTransport('SMTP',
{
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
const mailOptions = {
to: user.email,
from: 'email@school.edu',
subject: 'Password Reset',
text: `Hello etc etc`
};
smtpTransport.sendMail(mailOptions, (err) =>
{
if(err)
return reject(err);
resolve();
});
});
})
.then(() => res.sendStatus(200))
.catch((err) =>
{
//check if the error is the one from the DB where the user was not found
if(err == 404) {
return res.sendStatus(404);
}
return res.status(500).send(err);
});
});
bluebird是最流行的promise库之一。并且它提供了promise函数来将回调地狱转换为promise。
请阅读本文档并使用它。
http://bluebirdjs.com/docs/working-with-callbacks.html这个答案假定了4个完全连续的步骤:
(Function () {
Return Promise (function (resolve, promise) {
// try to do some stuff
if (success) {
Resolve ("pass this value on to next link in the chain");
}
Reject();
})()
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.catch (function (error) {
// handle any reject or any error in the chain
}
对于单个.then
,也可以选择返回一个Promise
。注意:.catch
块中的任何错误都将被吞噬。
这只是一个ES6
承诺但工作代码的例子。您可以进一步重构它以获得清晰的可重用代码。可以将ES5函数替换为ES6箭头函数
app.post('/forgotPassword', function(req, res, next) {
var catch = function(err){
return next(err);
}:
var error404 = function(){
return res.status(404).send();
};
var success = function(){
return res.status(200).send();
};
var step1 = new Promise(function(resolve, reject){
crypto.randomBytes(20, function(err, buf) {
if(err){
reject(err);
} else {
var token = buf.toString('hex');
resolve(token);
}
});
});
var step2 = function(token) {
// search for user with the given email
User.findOne({ email: req.body.email }, function(err, user) {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
return {error404: true};
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
return { error: err, token: token, user: user });
});
});
};
var step3 = function(obj) {
if(obj.error){
return catch(obj.error);
} else if(obj.error404) {
return error404();
} else {
var token = obj.token, user = obj.user;
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
var mailOptions = {
to: user.email,
from: 'email@school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
smtpTransport.sendMail(mailOptions, function(err) {
if(err){
return catch(err);
} else {
return success();
}
});
}
};
step1.then(step2, catch).then(step3);
}); // end POST route '/forgotPassword'
当你处理一个方法已经返回它们的库时,或者像bluebird这样的库允许你承诺现有的回调api,否则它会有很多转换。
如果你仍然设置用纯es6重构,把你的承诺包装器放在尽可能低的级别,为了获得最佳效果和最干净的错误处理,基本上是手动承诺:
let convert = (resolve, reject) => (err, res) => err ? reject(err) : resolve(res);
crypto.randomBytesAsync = n=>new Promise((y,n)=>crypto.randomBytes(n,convert(y,n)));
User.findOneAsync = u => new Promise((y, n) => User.findOne(u, convert(y, n)));
User.prototype.saveAsync = () => new Promise((y, n) => this.save(convert(y, n)));
然后像这样使用:
app.post('/forgotPassword', function(req, res, next) {
crypto.randomBytesAsync(20).then(buf => {
var token = buf.toString('hex');
// search for user with the given email
return User.findOneAsync({ email: req.body.email }).then(user => {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
res.status(404).send();
throw;
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
return user.saveAsync().then(() => {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: { user: config.sendgridUser, pass: config.sendgridPassword }
});
smtpTransport.sendMailAsync =
o => new Promise((y, n) => smtpTransport.sendMail(o, convert(y, n)));
return smtpTransport.sendMailAsync({
to: user.email,
from: 'email@school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
});
});
})
})
.then(() => res.status(200).send())
.catch(err => next(err));
}); // end POST route '/forgotPassword'
相关文章:
- 为什么同步睡眠功能没有被承诺内异步化
- 如何按照承诺使用mocha/chai/chai测试ES7异步函数
- Node JS异步承诺.所有问题
- 将异步工作流更改为承诺(蓝鸟)
- JQuery 承诺异步对话
- WinJS,从可能是异步的函数返回一个承诺,也可能不是异步的
- 同步异步创建的承诺
- 当我在异步操作Redux上开始单元测试时,没有定义错误承诺
- 使我的异步代码与setTimeout同步.我需要承诺吗
- 异步角度承诺和变量初始化
- 如何在节点中使用承诺一次并行异步多个请求
- 使用链式 Q 承诺异步加载的局部变量
- JavaScript 异步编程:承诺与生成器
- Ember:解决复杂的异步承诺
- 异步承诺中未处理的承诺拒绝
- ES6异步承诺
- 非异步承诺回调
- 使用Bluebird进行异步承诺处理
- 异步承诺失败时首选throw或reject
- Angular: Switch语句中的异步承诺