Question

I am new to NodeJS and coming from a PHP environment I am trying to figure out how to work with multiple callbacks. I do understand the basics about callback and I think it does make sens when writing modules. My problem is when comes the time to use those modules how to organize all the callbacks. Below is my implementation a request reset password controller method (I am using SailsJS). This is a first draft of my code. It was mainly to test a method of organizing callbacks. What do you guys think of this structure ? Is there a better way do it?

var _ = require('lodash'); 
var moment = require('moment');
var mailer = require("../../services/Mailer");
var crypto = require('../../services/crypto');
var forms = require("forms"),
    fields = forms.fields,
    validators = forms.validators;

module.exports = {
  // Request reset user password: submit form and send email
  request_process : function(req, res, next) {

    var form = createForm();

    form.handle(req, {

        // there is a request and the form is valid
        // form.data contains the submitted data
        success: function (form) {
            var user = null;
            var username = form.data.username;

            User.findOne({'username' : username}, foundUser);

            function foundUser( err, _user){
                if(err)
                    res.send(500, 'User not found');

                user = _user;

                if user.isPasswordRequestNonExpired()
                    return res.view('user/resetting/passwordAlreadyRequested');

                if !user.passwordRequestToken
                    user.passwordRequestToken = crypto.randomToken();

                renderEmail(null, user);
            }

            function renderEmail(err, user){
                res.render('email/resetting_check_email', {'user': user, }, sendEmail );
            }

            function sendEmail(err, template){
                if(err)
                    return res.send(500, "Problem with sending email");

                Mailer.send( user, "Reset Password", template, sentEmail);
            }

            function sentEmail(err, response){
                if(err)
                    return res.send(500, "Error sending email");

                user.passwordRequestedAt = moment().format();
                user.save(finish);
            }

            function finish(err){
                if(err)
                    return res.send(500);

                res.view();
            }               
        },

        // the data in the request didn't validate,
        // calling form.toHTML() again will render the error messages
        error: function (form) {
            console.log("registration error", form);

            res.locals.form = form;
            return res.render('user/registration/register');
        },

        // there was no form data in the request
        empty: function (form) {
            console.log("registration empty", form);

            res.locals.form = form;
            return res.render('user/registration/register');
        }
},

// Tell the user to check his email provider
check_email : function(req, res, next) {
    // if empty req.params.email
        // redirect request view

    // res.view('check_email.ejs')
},

// Reset user password
reset : function(req, res, next){
    // find userByPasswordToken

    // if !user
        // res.view ('invalid or expired "confirmation token".')

    // user.update password

    // res.view('reset.ejs');
},
Was it helpful?

Solution

Node.js callback basics:

Most of the functions (Node and its Libraries (called modules)), are of asynchronous (async) nature.

These functions have a common signature with callback as the last argument: function(arguments.....callback).

The callback is just another JavaScript function. ( yes, in Javascript, functions can be passed around as arguments to other functions). Node.js typical callbacks have a signature with first argument as error (if one happened): callback(err,outputs......).

example: first argument is a string, second an object (defined inline) and the last is a function (defined inline).

doSomeWork('running',{doFast:true,repeat:20}, function(err,result){
  if(err){
    console.log('ohnoes!);
  } else {
    console.log('all done : %s',result);
  }
});

which is equivalent to:

var type = 'running';
var options = {doFast:true,repeat:20};

var callback = function(err,result){
  if(err){
    console.log('ohnoes!);
  } else {
    console.log('all done : %s',result);
  }
};

doSomeWork(type,options,callback);

So the basic contract here is give a function its arguments and pass a callback to be invoked, when it is done. The passed call back will be invoked some where in future when there is something to return, error or the results.

Multiple nested callbacks are generally less readable and complex:

function uploadAll(path,callback){
  listFiles(path,function(err,files){
    if(err){
      callback(err);
    }else{
      var uploaded = [];
      var error;
      for(var i = 0 ; i < files.length; i++){
        uploadFile(files[i],function(err,url){
          if(err){
            error = err;
            break;
          }else{
            uploaded.push(url);
          }
        });
      }
      callback(error,uploaded);
    }
  }); 
};

But fortunately there are modules like async that help organize callbacks:

function uploadAll(path,callback){
  async.waterfall(
   [
     function(cb){
       listFiles(path,cb);
     },

     function(files,cb){
       async.map(files,uploadFile,cb);
     }

   ],callback);
}

Furthermore, there is a Promises pattern as well. Future versions support generators which provide many new async patterns.

OTHER TIPS

you can use async or q to manage the callback pyramids

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top