I can't seem to find a general solution to this problem, even though I feel like I can't be the first to come across this. I also suspect it might be I'm taking the wrong approach in the first place here. Let me know.
I have an Expressjs App the interacts with various APIs (mostly OAuth2). Once a request comes in the App checks if it has an access token to fetch data from an API. In case the token is expired it will request a new one.
Now, when my App receives a second request in the meantime, requiring the exact same API, I want to avoid making a second call for an access token.
What I do is use a "Collector" where callbacks can be stored for a given key. The first request to store a callback for a key gets a collector callback to invoke once it has finished the task. All subsequent callbacks are enqueued and called later on with the collector callback.
This is the simple Collector class (CoffeeScript)
# Collect callbacks for tasks and execute all once the job is done
module.exports = class Collector
constructor: ()->
@tasks = {}
add: (key, callback)->
unless @tasks[key]
# Initiate callback list for the key with first callback
@tasks[key] = [callback]
return ()=>
# Call all collected callbacks for the key
(callback.apply {}, arguments for callback in @tasks[key])
# Reset callback list
@tasks[key] = null
else
# Add callback to existing list
@tasks[key].push callback
return false
I'm not sure if storing the callbacks inside this class is the right way, but to use a database (Redis) I would have to find a way to store callbacks…
Is there a better way to invoke multiple callbacks once a job is done?
Why don't you just aggregate your callbacks into an array which you cycle through, executing each contained function when your original call is complete?
It could be as simple as:
var dones = [];
dones.push(function (err, res) { console.log('hoo'); });
dones.push(function (err, res) { console.log('ray!'); });
function whenDone(err, res) {
_.each(dones, function (done) { done(err, res); } });
}
myWhateverFunction(whenDone);
You can wrap this into whatever data structure you want, if you want to make it prettier.
I don't have a specific answer to your problem, but you should check out the async module. I think it's a step in the right direction: https://github.com/caolan/async