Mjuice Mjuice - 1 month ago 15
Javascript Question

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@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'


Answer

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