3

I have an array like this:

var names = ['Irina', 'Michael', 'Carl'];

I want to insert them into redis using transactions with promises (I don't know another way). But I'am confused about how to do this; this is my code:

var Promise = require("bluebird");
var redis = require("redis");

Promise.promisifyAll(redis.RedisClient.prototype);
Promise.promisifyAll(redis.Multi.prototype);

var client = redis.createClient(), multi;

var names = ['Irina', 'Michael', 'Carl'];

var result = names.map(function(item) {
    client.watch('user:id');
    client.getAsync('user:id', function(err, data) {
      var multi = client.multi();
      var user_id = parseInt(data) + 1;
      multi.hmsetAsync('user:' + user_id, 'username', item, 'about', 'love to coding');
      multi.incrAsync('user:id');
      multi.execAsync(function(err,data){
        console.log(data);
      });
    });
});

Promise.all(result).then(function(response) {
    console.log(response);
});

But this does not works (:

EDIT: This is the error thrown by the application:

Unhandled rejection Error: ERR wrong number of arguments for 'get' command

Someone, could help me please !


EDIT 2: I have change my code, but now only saves the last value of the array:

client.watch('user:id');
var result = names.map(function(item) {        
    var multi = client.multi();
    client.getAsync('user:id').then(function(value) {
        var user_id = parseInt(value) + 1;
        return user_id;
    }).then(function(user_id) {
        multi.hmsetAsync('user:' + user_id, 'username', item, 'about', 'love to coding');
        multi.incrAsync('user:id');
    }).then(function() {
        multi.execAsync().spread(function(err,data){
            console.log(data);
        });
    });
});

EDIT 3:

When using redis' MONITOR, this is the output:

[0 127.0.0.1:54439] "info"
[0 127.0.0.1:54439] "watch" "user:id"
[0 127.0.0.1:54439] "get" "user:id"
[0 127.0.0.1:54439] "get" "user:id"
[0 127.0.0.1:54439] "get" "user:id"
[0 127.0.0.1:54439] "MULTI"
[0 127.0.0.1:54439] "hmset" "user:88" "username" "Irina" "about" "love to coding"
[0 127.0.0.1:54439] "incr" "user:id"
[0 127.0.0.1:54439] "EXEC"
[0 127.0.0.1:54439] "MULTI"
[0 127.0.0.1:54439] "hmset" "user:88" "username" "Michael" "about" "love to coding"
[0 127.0.0.1:54439] "incr" "user:id"
[0 127.0.0.1:54439] "EXEC"
[0 127.0.0.1:54439] "MULTI"
[0 127.0.0.1:54439] "hmset" "user:88" "username" "Carl" "about" "love to coding"
[0 127.0.0.1:54439] "incr" "user:id"
[0 127.0.0.1:54439] "EXEC"

Executes the get user:id 3 times, and then the others methods. Why?

robe007
  • 3,523
  • 4
  • 33
  • 59

2 Answers2

2

I solved my issue in this way:

client.watch('user:id');
Promise.each(names, function(item) {
    var multi = client.multi();
    return client.getAsync('user:id').then(function(value) {
        var user_id = parseInt(value) + 1;
        return user_id;    
    }).then(function(user_id) {
        multi.hmsetAsync('user:' + user_id, 'username', item, 'about', 'love to coding');
        multi.incrAsync('user:id');
    }).then(function() {
        return multi.execAsync().spread(function(err,data){
            console.log(data);
        });
    });
})
.then(function() {
    console.log("Ended process ...");
});

The trick was changing the:

var result = names.map(function(item) {...});

with a Promise:

Promise.each(names, function(item) {...});

and then return the client.getAsync(user:id) to it.

And this is the output of redis' MONITOR:

[0 127.0.0.1:53290] "info"
[0 127.0.0.1:53290] "watch" "user:id"
[0 127.0.0.1:53290] "get" "user:id"
[0 127.0.0.1:53290] "MULTI"
[0 127.0.0.1:53290] "hmset" "user:88" "username" "Irina" "about" "love to coding"
[0 127.0.0.1:53290] "incr" "user:id"
[0 127.0.0.1:53290] "EXEC"
[0 127.0.0.1:53290] "get" "user:id"
[0 127.0.0.1:53290] "MULTI"
[0 127.0.0.1:53290] "hmset" "user:89" "username" "Michael" "about" "love to coding"
[0 127.0.0.1:53290] "incr" "user:id"
[0 127.0.0.1:53290] "EXEC"
[0 127.0.0.1:53290] "get" "user:id"
[0 127.0.0.1:53290] "MULTI"
[0 127.0.0.1:53290] "hmset" "user:90" "username" "Carl" "about" "love to coding"
[0 127.0.0.1:53290] "incr" "user:id"
[0 127.0.0.1:53290] "EXEC"

Amazing, but true ! (Clue: https://stackoverflow.com/a/25129878/2954267)

Community
  • 1
  • 1
robe007
  • 3,523
  • 4
  • 33
  • 59
0

Disclaimer: not a JS maven but I'll try helping :)

It looks like you want to have a unique ID per name. In the spirit of KISS I would try doing the following flow that doesn't call for any transactional behavior:

  1. id = INCR user:id
  2. HMSET user: ...

The "risk" here is that if your worker dies between 1 & 2, you "spend" an ID. If you can live with that, your could should probably look something like this (sorry, untested - see disclaimer ;)):

var result = names.map(function(item) {
    client.incrAsync('user:id').then(function(user_id) {
        client.hmsetAsync('user:' + parseInt(user_id), 'username', item, 'about', 'love to coding');
    }).then(function(err) {
        console.log(err);
    });
});

Update 1: but since you are amendment on transacting the for, I'm guessing that the last name's update, specifically the call to INCR, breaks the WATCH of the previous ones. Since you're not causing the error, you're not seeing this. You can also debug the behavior by using Redis' MONITOR while executing your code.

Update 2: I'm guessing that this how Node does things async - first it fires the GETs and stops when it needs to wait for the response. Then the first response (to the first GET) gets handled, then the 2nd... Besides messing with your intended logic, this also causes only the first bulk to enjoy the MULTI - the other two don't have a MULTI statement before their relative EXECs.

Itamar Haber
  • 47,336
  • 7
  • 91
  • 117