0
votes

I have the following action to create a user with node_redis:

server.post('/create_user', function(req, res, next) {
  console.log(req.body);
  var body = req.body;
  client.hincrby('users', 'count', 1, function(err, id) {
    client.hmset('user:'+id, 
    'username', body.username, 
    'password', body.password, 
    'email', body.email, function(err, write) { 
      client.hmget('user:'+id, 'username', 'email', function(err, read) {
        res.send({id: id, username: read[0], email: read[1]});
      });
    });
  });
})

I was thinking reading about Deferrable and Promisses here: http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/

How can this code be rewritten with Deferrables and Promisses, allowing cleaner exception processing and also better maitenance of the process?

The action are basically:

  1. Increase counter to get ID
  2. Set Redis hash of user with ID
  3. Return created user from Redis
2

2 Answers

3
votes

With promises you could do:

var Promise = require("bluebird");
//assume client is a redisClient
Promise.promisifyAll(client);

server.post('/create_user', function(req, res, next) {
    console.log(req.body);
    var body = req.body;
    client.hincrbyAsync('users', 'count', 1).then(function(id){
        return client.hmsetAsync(
            'user:' + id,
            'username', body.username,
            'password', body.password,
            'email', body.email
        );
    }).then(function(write){
        return client.hmgetAsync('user:'+id, 'username', 'email');
    }).then(function(read) {
        res.send({id: id, username: read[0], email: read[1]});
    }).catch(function(err){
        res.writeHead(500);
        res.end();
        console.log(err);
    });
});

Not only will this perform better than waterfall, but if you have a synchronous exception, your process won't crash, instead even synchronous exceptions are turned into promise rejections. Although I am pretty sure the above code will not throw any such exceptions :-)

1
votes

I've become a fan of the async library. It's pretty performant and has an excellent methodology for cleaning up readability.

Here's what your example would look like rewritten with the async waterfall function.

The basic setup for waterfall is:

async.waterfal([ array of functions ], finalFunction);

Note: the waterfall method expects callback functions to always have the first param to be an error. The great thing about this, is that if an error is returned at any step, it goes directly to the completion function with the error.

var async = require('async');

server.post('/create_user', function(req, res, next) {
  console.log(req.body);
  var body = req.body,
      userId;

  async.waterfall([
    function(cb) {
      // Incriment
      client.hincrby('users', 'count', 1, cb);
    },
    function(id, cb) {
      // Higher Scope a userId variable for access later.
      userId = id;
      // Set
      client.hmset('user:'+id, 
        'username', body.username, 
        'password', body.password, 
        'email', body.email, 
        cb);
    },
    function(write, cb) {
      // Get call.
      client.hmget('user:'+userId, 'username', 'email', cb);
    }
  ], function(err,read){
      if (err) {
        // If using express:
        res.render('500', { error: err });
        // else
        res.writeHead(500);
        res.end();
        console.log(err);
        return;
      }
      res.send({id: userId, username: read[0], email: read[1]});
  })
})