I am working on a social network graph, where I want to build a "six degrees of separation" tree based on adjacency lists that I get from an API.
For each person, the API will return an array of friends in the form [id1, id2, id3...] which is exactly what I want. But the catch is that there are a huge number of people, and the API only allows 400 calls/15 minutes. I can save the data in a local db, but I don't want to flood the API with requests.
The pseudocode for what I am doing is be something like this:
requestCharacter = function(id) {
is this person in my db already? if true, return;
else make api call(error, function(){loopFriends(character)}) {
save character in database
}
}
loopFriends(character){
foreach(friend in character.friends) requestCharacter(friend);
}
And I've coded that, more or less, and it works ok, but since it keeps traversing the trees, and since people recur in one another's friend lists, it is grossly inefficient, and keeps busting the API limits
So what I want to do instead is to queue up the requests, check that something isn't in the queue yet before I add it, and run the queue in batches of 400 or fewer requests at a time. (So if the queue has 1200 in it it would run 400, wait 15 mins, run 400, wait 15 mins, run 400...)
I tried using async.js with its queue, and I was able to load a ton into the queue, but I don't think it ever actually ran. What's the best approach for a situation like this?
My actual non-queued code follows:
var lookupAndInsertCharacter = function(id){
Character.findOne({ 'id': id }, function (err, person) {
if (err) console.log(err);
else {
if(person!=null) {console.log('%s already exists in database, not saved', person.name); getCharacterFriends(id);}
else insertCharacter(id, function(){getCharacterFriends(id)});
};
})
}
var insertCharacter = function(id, callback){
var url = getCharacterURL(id);
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
var result = JSON.parse(body);
if(result.status_code != 1 ) {console.log("ERROR status_code: %s. Please wait 15 minutes", result.status_code); return;}
else {
var me = new Character(processCharacter(result));
me.save(function(err){
if (err) return handleError(err);
});
console.log("Saved character "+me.name);
}
}
else {
console.log(error);
}
});
}
var getCharacterFriends = function(id) {
Character.findOne({ 'id': id }, function (err, person) {
if (err) console.log(err);
else {
console.log("Getting friends for %s",person.name);
_.each(person.character_friends, function(d){
lookupAndInsertCharacter(d);
});
console.log("Getting enemies for %s",person.name);
_.each(person.character_enemies, function(d){
lookupAndInsertCharacter(d);
})
};
})
}