having a function that needs to call an indeterminate number of other (potentially asynchronous) functions, each with callback, I am using the following pattern.
While pretty certain it is correct, not very pleasing aesthetically.
// fn responsible for figuring out and calling aysnc funcs function fn( arg, outNext ) { // calculate how many funcs we will be calling var waitCt = 0; var a, b, c; if( a = arg['a'] ) waitCt++; if( b = arg['b'] ) waitCt++; if( c = arg['c'] ) waitCt++; // call funcs if( a ) fnArbitrary( arg.a, inNext ); if( b ) fnRandom( arg, inNext ); if( c ) fnClueless( 15, inNext ); // calback func function inNext( err ) { // wait for one less func waitCt--; // return err if found if( err ) outNext( err ); // return nada if all funcs performed if( waitCt == 0 ) outNext(); // otherwise we're waiting for other funcs to finish return; } }
To me it seems necessary to take two steps: the first to see how many funcs will be called, and the second to do the calling. But perhaps my brain is addled from old-school programming or having read too much Lear last night.
The best management idea I've seen for multiple aysnc functions is Deferreds and Futures as described in this Script Junkie article, this article about the FutureJS implementation, this msdn article and this Stack Overflow question.
It's actually a structured way of thinking about sequencing multiple asynchronous calls or defining execution dependencies between them and it seems to have traction with multiple implementations underway (already usable) for different frameworks so it seems like a generally supported idea that will be useful to learn both now and in the future.
Managing multiple async calls with dependencies between them is indeed messy by hand. Not only does the code look messy, but it's impossible to read and even more impossible to use a debugger on. Debugging typically requires dumping lots of info to a log file and then trying to sort out what happened. If timing is involved, then you're really in for a mess. In my last project, the only area that I still have an unresolved bug is some sort of missed dependency between four different async calls at startup. I've fortified the code to make it so the error almost never happens, but it isn't quite gone yet. Next step is to switch to using deferreds and add some formal structure to it.
As for some of the bigger libraries, we have jQuery Defereds, YUI3 has Async Queue (which is less powerful than general deferreds, but useful), Dojo has a Deferred object and there are a couple deferred libraries that aren't tied to a main library.
I have an after utility function.
This should be used for simple cases. For more complex situations use some form of flow control like Futures