Happydevdays Happydevdays - 1 month ago 9
Javascript Question

node.js / express app - which async method to use to replace my nested calls to HGET?

Background

I'm just learning node js and have run into a situation where I need to make up to two back to back calls to my redis database, depending on the results of the first query.

The code I have right now works.. but it's very ugly. I wrote it this way because I'm not good with async 'stuff'. But now that it's working... I want to refactor in a way that is readable and of course, in a way that works.

Here's the code, along with an explanation of what I'm trying to do:

Code

router.get('/:ip', function(req, res, next) {
var ip = req.params.ip;
if ( ! validate_ipV4(ip) ) {
res.status(400).send("Invalid IP");
return;
}

var three_octets = extract_octets(ip, 3);
var two_octets = extract_octets(ip, 2);
if (debug) { winston.log('info', 'emergency router.get() attempting hget using :' + three_octets); }
redis.hget("e:" + three_octets, 'ccid', function (e, d) {
if (e){
winston.log('error', 'hget using key: ' + octets + ' failed with error: ' + e);
res.status(500).send("Database query failed");
return;
}
if (d) {
if (debug) { winston.log('info', 'HGET query using ip: ' + ip + ' returning data: ' + d ) };
res.status(200).send(JSON.stringify(d));
return;
} else {
//retry using only 2 octets
redis.hget("e:" + two_octets, 'ccid', function (e, d) {
if (e){
winston.log('error', 'hget using key: ' + octets + ' failed with error: ' + e);
res.status(500).send("Database query failed");
return;
}
if (d) {
if (debug) { winston.log('info', 'HGET query using ip: ' + ip + ' returning data: ' + d ) };
res.status(200).send(JSON.stringify(d));
return;

}else {
res.status(404).send("Unknown IP");
return;
}
});//end hget
}
});//end hget

});


Explanation:

Accept an ip address as input. 10.1.1.1

Try to query the database for a hash that matches the first three octets. For example: "hget e:10.1.1 ccid"

If i have a match, I can return the db results and exit. otherwise, if the query came back with no results, then I need to retry using the first two octets: "hget e:10.1 ccid"

if that returns nothing, then i can exit the GET method.

ASYNC

I know that there is an async module... and i've tried to use MAP before. But from what I understand, you cannot force MAP to exit early.
So for example, if I did something like this:

async.map(ipOctets, hash_iterator, function (e, r) {
})


where ipOctets was an array with both 10.1.1. and 10.1 in it, if the first query found a match in the database, there's no way I can stop it from running the second query.

Can you give me some pointers on how to improve this code so that I don't have to repeat the same code twice?

I also thought of putting the redis.hget call into a separate function... like this:

var hash_get = function (hash, key, field) {
if (debug) { winston.log('info', 'hash_get() invoked with : ' + hash + ' ' + key + ' ' + field);}
redis.hget(hash + key, field, function (e, d) {
if (e){
winston.log('hash_get() failed with: ' + e);
return 500;
}
if (d) {
return (d);
}else {
return 404;
}
});
}


But again, I'm not sure how to do the following in a synchronous way:


  • call it from router.get

  • check results

  • repeat if necessary



Sorry for the noob questions.. but any pointers would be appreciated.

EDIT 1

Since posting, i found this http://caolan.github.io/async/docs.html#some
and I'm currently testing to see if this will work for me.

But please comment if you have some suggestions!
Thanks.

Answer

Maybe like this:

router.get('/:ip', function (req, res, next) {
    var ip = req.params.ip;
    if (!validate_ipV4(ip)) {
        res.status(400).send("Invalid IP");
        return;
    }

    var three_octets = extract_octets(ip, 3);
    var two_octets = extract_octets(ip, 2);
    //if (debug) { winston.log('info', 'emergency router.get() attempting hget using :' + three_octets); }

    var hash = "e:"
    var field = 'ccid';

    async.waterfall([
    function (callback) {
        hash_get(hash, three_octets, field, callback)
    },
    function (d, callback) {
        if (d) {
            callback(null, d);
            return;
        }

        hash_get(hash, two_octets, field, callback)
    }
    ], function (err, result) {
        if (err) {
            winston.log('error', err.message);
            res.status(err.status).send(err.message);
            return;
        }
        if (result) {
            res.status(200).send(JSON.stringify(result));
            return;
        }
        res.status(404).send("Unknown IP");
        return;
    });
});

var hash_get = function (hash, key, field, callback) {
    if (debug) { winston.log('info', 'hash_get() invoked with : ' + hash + ' ' + key + ' ' + field); }
    redis.hget(hash + key, field, function (e, d) {
        if (e) {
            callback({ status: 500, message: 'hget using key: ' + key + ' failed with error: ' + e });
            return;
        }

        if (d) {
            if (debug) { winston.log('info', 'HGET query using ip: ' + ip + ' returning data: ' + d) };
            callback(null, d);
        } else {
            callback(null, null);
        }
    });
}
Comments