I'm building a little Node.js app, and one of the things I need to do is fetch all of the data in the Redis datastore and display it on this specific page. So far, my code looks like this:
exports.view = function (req, res) {
rclient.keys('*', function (err, reply) {
var data = {};
// Get the value for each key
for (var i = 0; i < reply.length; i++) {
rclient.GET(reply[i], function (err, value) {
data[reply[i]] = value;
});
}
res.render('view', {title: 'Datastore', data: data});
});
};
I have at least two problems here. One: Because of the asynchronous nature of Node.js, the data variable is empty when I pass it to my render function. What is the proper way to avoid this?
Two: The variable i is not equal to the expected value in my callback because the loop has incremented, and because of scoping, i is equal to 1 by the time the callback for the first iteration is called (it should be 0).
I'm pretty new to Node.js, so I'm sure I'm doing something wrong.
Consider the following function:
exports.view = function (req, res) {
rclient.keys('*', function (err, reply) {
var data = {};
var count = reply.length;
reply.forEach( function(key) {
rclient.get( key, function (err, value) {
data[key] = value;
--count;
if (count <= 0) {
res.render('view', {title: 'Datastore', data: data});
}
});
});
});
};
To solve your first problem, you need to call the final statement in the inner callback, once all the results have been received. You can make sure of that by counting the expected items, and call the render statement only after the last one has been processed.
To solve your second problem, you can use the forEach statement which introduces another functional scope. In Javascript, the scope of closures is defined at function level, not at block level. Replacing for loops by forEach is an elegant way to work this problem around.
Last point: using the KEYS command in a real application is a very bad practice. KEYS is meant to be used as a debugging facility only.
I ended up building on Didier's response, and using async.js
exports.view = function (req, res) {
// Get all of the key information for the given key
var getKeyInfo = function (key, callback) {
async.parallel([
rclient.GET.bind(rclient, [key]),
rclient.TTL.bind(rclient, [key]),
rclient.TYPE.bind(rclient, [key])
], callback);
}
// Fetch all keys in our data store
rclient.KEYS('*', function (err, keys) {
var asyncCallbacks = {};
// Build an array of callbacks for our async operation
for (var i = 0; i < keys.length; i++) {
asyncCallbacks[keys[i]] = async.apply(getKeyInfo, keys[i]);
}
// Asynchronously get the data for each key in the data store
async.parallel(asyncCallbacks, function (err, results) {
res.render('view', {title: 'Datastore', data: results});
});
});
};