我如何重构瀑布方法,从异步到使用ES6承诺?

How could I refactor the waterfall method from async to use ES6 promises?

本文关键字:异步 承诺 ES6 何重构 重构 布方法 方法      更新时间:2023-09-26

我有一个路由,允许用户通过发送电子邮件重置他们的密码。大多数网站的标准程序。在这条路由中,我导入了async npm模块,并使用瀑布方法,这样我就可以处理多个函数的异步特性。我仍然很难理解承诺,但我正在尝试用承诺或承诺链取代瀑布。

我如何用承诺重构这个路由?以下是这条路线中包含的步骤,目前分为4个瀑布功能。

  1. 首先路由创建一个重置令牌
  2. 根据email搜索用户2.5. 如果找到用户,保存用户,否则返回404
  3. 向用户发送包含重置url的邮件
  4. 返回状态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'