1

Im creating an a API using NodeJS with the express framework and mongodb to store my data.

I have a register function which does 3 main things.

  1. Creates the new.
  2. Creates a token and associates it with a user.
  3. Sends an email.

    module.exports.register = function(req, res) {

    var input = req.body;
    var token = uuid.v4();
    
    
    // Create a new user
    var user = new User ({
    
        username: input.username,
        email: input.email,
        password: input.password,
        active: false
    
    });
    
    user.save(function(err) {
    
        if(err) return res.json({success: false, errors: 'Failed To Create User'});
    
    });
    
    
    // Create a new Token
    var newToken = createToken('new', null, user._id);
    
    
    // Assign New Token To New User
    if(newToken) {
    
    
        user.tokens.push(newToken._id);
    
    
        user.save(function(err) {
    
            if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
    
        });
    
    }
    
    
    // Send Email To User
    var mailData = {
    
        from: 'deleted@hotmail.com',
        to: input.email,
        subject: 'Activate Your Account',
        text: 'http://localhost:8080/api/auth/activate/' + token
    
    }
    
    mail.messages().send(mailData, function(err, body) {
    
        if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
    
    });
    
    
    return res.json({ 
        success: true, 
        status: 'Successfully Registered User, Check Email To Activate' 
    });
    

    }

Now even if there are errors whilst creating the user or the token or sending an email. It's always going to return that it successfully registered a user. How can i restructure / handle this better?

I also have the problem where if the email fails to send the user and token will have already been created, how do i solve this issue? Would i just create a resend activation function?

  • 1
    You have to nest your sequential async operations so the next async operation doesn't start until you are inside the callback that indicates it finished successfully. – jfriend00 Jan 30 '16 at 10:25
  • @jfriend00 can you give me a a small example of how to do this? This maybe what im looking for. –  Jan 30 '16 at 10:47
  • You already have an answer that shows it. – jfriend00 Jan 30 '16 at 11:05

2 Answers2

2

You mention that it's always going to return that it successfully registered a user. It will also send the email even if the token creation failed.

One (not very pretty) way to do it would be to continue with the next step inside the callback function of the previous step:

user.save(function(err) {
  if(err) {
    return res.json({success: false, errors: 'Failed To Create User'});
  } else {
    // Create a new Token
    var newToken = createToken('new', null, user._id);
    // Assign New Token To New User
    if(newToken) {
      user.tokens.push(newToken._id);
      user.save(function(err) {
        if(err) {
          return res.json({success: false, errors: 'Failed To Save User Token'});
        } else {
          // Send Email To User
          var mailData = {
            from: 'deleted@example.com',
            to: input.email,
            subject: 'Activate Your Account',
            text: 'http://localhost:8080/api/auth/activate/' + token
          }

          mail.messages().send(mailData, function(err, body) {
            if(err) {
              return res.json({ success: false, errors: 'Failed To Send Email' });
            } else {
              return res.json({ 
                success: true, 
                status: 'Successfully Registered User, Check Email To Activate' 
              });                
            }
          });
        }
      });
    }
  }
});

As you can see, it looks like a callback-piramyd-of-doom very fast, but it only sends the success response when all the previous steps have completed.

You should also add the else case when the newToken is not created.

reacuna
  • 463
  • 9
  • 17
1

You should remove final return statement (from the end of your code) and return at the correct place inside each callback if there is no error.

If you send your response in the body of the function your callbacks will never get the chance to run. Therefore you must nest your callbacks and only call res.send if you are

  • returning early due to an error or
  • if everything is complete.

e.g.

// Create a new user
var user = new User ({
    username: input.username,
    email: input.email,
    password: input.password,
    active: false
});

user.save(function(err) {
    if(err) return res.json({success: false, errors: 'Failed To Create User'});

    // Create a new Token
    var newToken = createToken('new', null, user._id);
    // Assign New Token To New User
    if(newToken) {
        user.tokens.push(newToken._id);
        user.save(function(err) {
            if(err) return res.json({success: false, errors: 'Failed To Save User Token'});

            // Send Email To User
            var mailData = {
                from: 'deleted@hotmail.com',
                to: input.email,
                subject: 'Activate Your Account',
                text: 'http://localhost:8080/api/auth/activate/' + token
            }

            mail.messages().send(mailData, function(err, body) {
                if(err) return res.json({ success: false, errors: 'Failed To Send Email' });

                return res.json({
                    success: true,
                    status: 'Successfully Registered User, Check Email To Activate'
                });
            });
        });
    }
});

Asynchronous alternatives

Unfortunately, with node.js you should get used to and understand callbacks; even if you end up using something else most of the time. The way your code was structured was neater and logical but does not work in node.js because you have to wait for the callbacks to complete before you can return from your function.

However, callbacks are the default but one of the worst mechanisms for handling asynchronous logic. If you want to structure the code differently you have quite a few options. Here are just a couple:

  1. Use promises instead of callbacks.

In your case your database library (mongoose? sequelize?) should have something built in that allows you to write your code like this:

user.save()
  .then(function () {
      // step 1
  })
  .then(funciton () {
      // step 2
  })
  .done()

This style of programming is well worth learning and will make your code more readable than callbacks. callbacks vs promises

  1. Use Koa instead of express.

Koa, is the next generation of express written by the same people. It uses generators instead of callbacks which means you can write code that looks more like this:

// this is just an example
var result = user.save();
if (result.error) return res.send({success : false, ...});

user.token = getNewToken();
user.update();
if (result.error) return res.send({success : false, ...});    

return res.send({success : true, message : "Good news, no errors"});

Generators/(aka async functions) are the direction Javascript is moving in but there is a learning curve to start using. Behind the scenes there is something very complex going on to make asynchronous code appear exactly like synchronous code. Basically, the functions know how to pause execution until they are required again.

Start with callbacks

Like I say, callbacks are not that nice. However, you should get used to using them. They are the basic building blocks of node.js and it take a while to get comfortable with better alternatives. It's also important to get used to them because otherwise you won't appreciate why the alternatives are better.

Good luck and watch out for callbacks inside loops :)

Community
  • 1
  • 1
chriskelly
  • 7,526
  • 3
  • 32
  • 50
  • But to me it doesent make sense having that successful response there because it still has to do the rest of the code like sending an email? –  Jan 30 '16 at 10:46
  • @KHAN: Did you try with the complete example and did it work? – chriskelly Jan 30 '16 at 12:40
  • yeah i just tried it it works. http://pastebin.com/zzwsi3Hr i changed my createtoken to a call back there are just so many nested callbacks i was hoping for a better way? –  Jan 30 '16 at 13:01
  • Definitely there are better ways but step 1 : fix whats wrong :). I will add a comment at the bottom about nicer approaches – chriskelly Jan 30 '16 at 13:05
  • yeah deffi, thanks for the help. I definitely want to look into a nicer approach. –  Jan 30 '16 at 13:11
  • do promises work in IE? and should thye be used over callbacks when ever possible? –  Jan 30 '16 at 17:05
  • nope. Not without some polyfill. http://caniuse.com/#feat=promises . If you use babel or similar (maybe typescript) they should work. – chriskelly Jan 30 '16 at 18:08