0

I am trying to extract values out of a nested for-loop. My loop takes values from Redis, and I want to add these values to an array variable called "info".

The important bit is the for-loop.

app.get('/query', function(req, res) {

    var info = [];

    redisClient.keys("todo:*", function(err, data) {            

        if (err) return console.log(err);

        for (var i = 0, len = data.length; i < len; i++) {
            var id = data[i];
            var listItem, author, goodness;

            redisClient.hgetall(data[i], function(err, obj) {
                listItem = obj.listItem;
                author = obj.author;
                goodness = {
                    id: id,
                    listItem: listItem,
                    author: author
                }
                info.push(goodness);
                console.log("In here: " + info);

            }); 
            console.log("Out here: " + info);
        }
        console.log("Way out here: " + info);
    }); 
    console.log("WAY THE HECK OUT HERE: " + info);
});

Basically, I want the values in the variable "goodness" to be pushed to an array variable called "info". when I execute the code, the info array gets filled up here,

console.log("In here: " + info);

but I have not found a way to extract the info array to have values outside of the redisClient.hgetall() function. I have tried return statements to no avail, though as a beginning programmer there is a decent chance I'm not doing these properly.

NOTE: I have tried to take guidance from the original answer and I must be doing something wrong, or the solution given wasn't good enough, or both. I have added the Q library to my project, and have tried to find a solution. Here is my current code:

app.get('/query', function(req, res) {

    var redisKeys = Q.nbind(redisClient.keys, redisClient);
    var redisHGetAll = Q.nbind(redisClient.hgetall, redisClient);
    var id, listItem, author;

    var info = [];

    redisKeys('todo:*').then(function (data) {
        console.log("First: " + data);
       var QAll = Q.all(data.map(processKeysFunc(info)));
       console.log("Reading within this: " + data);
       console.log("Next within this: " + QAll);
    }, function (err) { 
        if (err) return console.log(err);
        }).then(function () {
       console.log("Finally: " + data);

    })();

    function processKeysFunc(array) {
        return function (key) {
            console.log("This is the key: " + key);
            return redisHGetall(key).then(function (obj) {
                console.log("This is the obj: " + obj);
                array.push({
                    id: obj.id,
                    listItem: obj.listItem,
                    author: obj.author
                });
            });
        };
    }
});

And this is what I get within my console.log:

First: todo:281f973d-6ffd-403b-a0f4-9e8958caed35,todo:7ed8c557-0f15-4555-9119-
6777e1c952e8,todo:eb8dbee1-92ca-450e-8248-ad78548cd754,todo:712e8d27-bf9b-46f0-bfdd-
c53ef7d14441,todo:301dd91a-2b65-4b87-b129-a5ad569e38e5,todo:720d98b8-bdec-446d-a178-
fb7e264522aa,todo:d200c6cf-2ee5-443b-b7dc-d245c16899c8,todo:8169e9af-0204-42c8-9ddf-
3b00f7161b11

This is the key: todo:281f973d-6ffd-403b-a0f4-9e8958caed35

1 Answer 1

1

node.js is generally non-blocking, this is why callbacks are used. The callback passed to .hgetall will only execute when the data from redis has been fully received. The rest of the code around it will execute immediately and not wait for the data from redis. In fact, since the .hgetall call likely involves IO the callback will always run after the code around has executed.

You have a few options, including using Promises (https://github.com/kriskowal/q).

I'll suggest a solution that should be comprehensible the current structure of your code:

app.get('/query', function(req, res) {

    var info = [];
    var completed = 0;

    redisClient.keys("todo:*", function(err, data) {            

        if (err) return console.log(err);

        for (var i = 0, len = data.length; i < len; i++) {
            var id = data[i];
            var listItem, author, goodness;

            redisClient.hgetall(data[i], function(err, obj) {
                completed++;
                if (err) return console.log(err);

                listItem = obj.listItem;
                author = obj.author;
                goodness = {
                    id: id,
                    listItem: listItem,
                    author: author
                }
                info.push(goodness);
                console.log("In here: " + info);

                if (completed === data.length) {
                    // Do something with info here, all work has finished
                }

            }); 
            console.log("Out here: " + info);
        }
        console.log("Way out here: " + info);
    }); 
    console.log("WAY THE HECK OUT HERE: " + info);
});

The key bit is the new variable completed that tracks how many callbacks have returned.

I would highly recommend using promises instead, though. Something like:

var Q = require('q');

var redisKeys = Q.nbind(redisClient.keys, redisClient);
var redisHGetall = Q.nbind(redisClient.hgetall, redisClient);

app.get('/query', function(req, res) {
    var info = [];
    redisKeys('todo:*').then(function (data) {
        return Q.all(data.map(processKeysFunc(info));
    }, function (err) { /* handle error */ }).then(function () {
       console.log('Complete info array=%j', info);
       res.setHeader('Content-Type', 'application/json');
       res.end(JSON.stringify(info));
    });
});

function processKeysFunc(array) {
    return function (key) {
        return redisHGetall(key).then(function (obj) {
            var goodness = {
                id: key,
                listItem: obj.listItem,
                author: obj.author
            };
            array.push(goodness);
        });
    }
}

processKeysFunc returns a function so that you can cleanly pass the info array around without needing to define every function inline. If you prefer inline, that works too though.

Sign up to request clarification or add additional context in comments.

5 Comments

I will test out Q and see how well it works. THank you.
I tried your second method, I could not make it work. Maybe I did something wrong?
The answer I provided is not a complete working example. Instead it shows part of the logic. I'll see if I can make it more complete. I'm not testing it out, though, so I can't promise it'll work as-is
I've updated the second method - if it still doesn't work please share a gist of your attempt
That did just the trick! You are a scholar and a gentleman. Thank you for going through and parsing through my code to figure out a solution. I will study this and put it to good use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.