Search code examples
javascriptes6-promise

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


I have a route that allows a user to reset their password by sending them an email. Standard procedure for most websites. In this route, I import the async npm module and use the waterfall method so that I can handle the asynchronous nature of multiple functions. I'm still having a bit of trouble understanding promises, but I'm trying to replace waterfall with a promise or promise chain.

How could I refactor this route with a promise? Here are the steps contained in this route that is currently split up into 4 functions with waterfall.

  1. First the route creates a reset token
  2. Search for user based on email 2.5. If user is found, save user, otherwise return 404
  3. Send email to user containing a reset url
  4. Return a status of 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 protected]',
                  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'
    

Solution

  • Promise is a very powerful tool. It can be hard to understand it at the beginning, but totally worth the effort! Please let me know if you have any doubts :)

    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 protected]',
                  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);
        });
    });