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

时间:2016-10-14 05:07:09

标签: javascript es6-promise

我有一条路线允许用户通过向他们发送电子邮件来重置密码。大多数网站的标准程序。在这个路由中,我导入异步npm模块并使用瀑布方法,以便我可以处理多个函数的异步性质。我仍然在理解承诺方面遇到一些麻烦,但我试图用承诺或承诺链取代瀑布。

我怎么能以承诺重构这条路线?以下是此路线中包含的步骤,目前通过瀑布分为4个功能。

  1. 首先,路线会创建重置令牌
  2. 根据电子邮件搜索用户 2.5。如果找到用户,请保存用户,否则返回404
  3. 向包含重置网址的用户发送电子邮件
  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'
    

5 个答案:

答案 0 :(得分:3)

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);
    });
});

答案 1 :(得分:1)

bluebird是最受欢迎的承诺库之一。 它提供了promisify函数来将回调地狱转换为承诺。

请阅读本文档并使用它。

http://bluebirdjs.com/docs/working-with-callbacks.html

答案 2 :(得分:1)

这个答案假设有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块中的任何错误。

答案 3 :(得分:0)

这只是ES6承诺但工作代码的示例。您可以进一步重构它以获得清晰的可重用代码。您可以使用ES6箭头功能替换ES5功能。

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'

答案 4 :(得分:0)

当你正在处理已经返回方法的库时,或者像bluebird这样的库可以让你宣传现有的回调API,Prom的工作效果最好,否则就会有大量的转换。

如果您仍然使用简单的es6进行重构,请将您的promise-wrappers放在尽可能低的级别,以获得最佳效果和最干净的错误处理,基本上是手动promisify:

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'