Jakub Kolčář
Jakub Kolčář

Reputation: 304

Calling redis method synchronously

I am learning developing in nodejs and redis db. I am on express framework.

Problem: Let's say in redis I have some user info in hashes with key user:id, than I have last logged in key user_lastlogged:user_id and than I have his favourite items in user_favourites:user_id.

I want to render page with user details, last login time and his fav. items in node js.

I would use something like this, but of course it wont work, because the method callbacks are run asynchronously.

var redis = require("redis");
var client = redis.createClient();

router.get('/', function (req, res, next) {

  var userId='1';

  var returnHtml = '';

  client.hgetall('user:'+userId, function(err,objects){
    returnHtml+=utils.inspect(objects);
  });

  client.hgetall('user_lastlogged:'+userId, function(err,objects){
    returnHtml+=utils.inspect(objects);
  });

  client.hgetall('user_favourites:'+userId, function(err,objects){
    returnHtml+=utils.inspect(objects);
  });

  res.send(returnHtml);

});

Ignore the use of appropriate redis data types etc for now please.

How is this kind of task generally solved in node.js? Or maybe in express js framework (if it helps)?

Thanks!

Upvotes: 1

Views: 3043

Answers (2)

Reza
Reza

Reputation: 867

Disclaimer: I would solve this with promises which are much cleaner. If you are curious, ask and I will provide the answer.

but, for what you asked, here:

router.get('/', function (req, res, next) {

  var userId='1';

  var returnHtml = '';

  client.hgetall('user:'+userId, function(err,objects){
    returnHtml+=utils.inspect(objects);
    client.hgetall('user_lastlogged:'+userId, function(err,objects){
       returnHtml+=utils.inspect(objects);
       client.hgetall('user_favourites:'+userId, function(err,objects){
           returnHtml+=utils.inspect(objects);
           res.send(returnHtml);
       });           
    });
  });
});

update: Bluebird answer was provided in another post. I am a Q user so would do it with q this way:

var q = require('q');

var promiseHGetAll = function(key, htmlFragment){ //making a promise version for the function call.

    if(!htmlFragment) htmlFragment=""; //optional argument, of course

    var deferred = q.defer(); //a deferred is an object that has a promise in it. and some methods
    client.hgetall(key,deferred.makeNodeResolver()); 
    //makeNodeResolver is for node methods that have 
    //function(err,result) call back. if the function has an error, 
    //the promise will be rejected and err will be passed to it.
    // if there is no err, the promise will be resolved and result 
    // will be passed to it.

    return deferred.promise.then(function(objects){
        //the first argument to then() is a function that is called
        //if the promise succeeds. in this case, this is objects returned
        // from Redis.
        return htmlFragment + utils.inpect(objects);
        // this function can return a promise or a value.
        // in this case it is returning a value, which will be
        // received by the next .then() in the chain.
    });
}

router.get('/', function(req,res){
   var userId = "1";
   promiseGetAll("user"+userId).then(function(htmlFragment){
      //this is called when the promise is resolved, and the 
      //utils.inspect(objects) is called and the return value is 
      //supplied to this then()
      return promiseGetAll("user_lastlogged"+userId, htmlFragment);
      //.then() functions can return a promise. in this case, the
      // next .then will be called when this promise is resolved or rejected.

   }).then(function(withUserLastLogged){

      return promiseGetAll("user_favourites"+userId,withUserLastLogged);

   }).then(function(returnHTML){

      res.send(returnHTML);

   }).catch(function(error){
      //this will be called if there is an error in the node calls,
      // or any of the previous .then() calls throws an exception,
      // or returns a rejected promise. it is a sugar syntax for 
      // .then(null, function(err){..})
      res.status(503).send(error);
   }).done(); //throw an error if somehow something was escaped us. shouldn't happen because of catch, but force of habit.
})

Upvotes: 2

Boris Charpentier
Boris Charpentier

Reputation: 3545

In node most of the code is asynchronous, so you will encounter this use case a lot.

Basically, you should use the callback to chain operation.

var redis = require("redis");
var client = redis.createClient();

router.get('/', function (req, res, next) {

  var userId='1';

  var returnHtml = '';

  client.hgetall('user:'+userId, function(err,objects){
    returnHtml+=utils.inspect(objects);
    client.hgetall('user_lastlogged:'+userId, function(err,objects){
      returnHtml+=utils.inspect(objects);
      client.hgetall('user_favourites:'+userId, function(err,objects){
        returnHtml+=utils.inspect(objects);
        res.send(returnHtml);
      });
    });
  });
});

As you can see it's a bit of callback hell, you may look into https://github.com/NodeRedis/node_redis#user-content-promises to promisify the calls to make it more readable.

With bluebird it may look like :

var bluebird = require('bluebird');
var redis = require('redis');
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);

var client = redis.createClient();

router.get('/', function (req, res, next) {

  var userId='1';

  var returnHtml = '';

  client.hgetallAsync('user:'+userId)
  .then(function(objects){
    returnHtml += utils.inspect(objects);
    return client.hgetallAsync('user_lastlogged:'+userId);
  })
  .then(function(objects){
    returnHtml+=utils.inspect(objects);
    return client.hgetallAsync('user_favourites:'+userId);
  })
  .then(function(objects){
    returnHtml+=utils.inspect(objects);
    res.send(returnHtml);
  })
  .catch(function(err){
    //manage error
  });

});

Upvotes: 3

Related Questions