I'm writing some JavaScript that interacts with a browser plugin (add-on in FF, ActiveX in IE). Some of the calls are asynchronous and trigger an event when complete. There are certain cases where I need to chain calls together, but need to wait for the first call to complete before initiating the second call. For instance:
A call to attach a device needs to first check if a device is already attached, and disconnect it. So the calls would be:
pluginObject.disconnectDevice(deviceKey);
pluginObject.connectDevice(deviceKey, backingInfo);
Just calling them straight up like this would fail because a connect is initiated before the disconnect is actually complete, and the plugin can't handle that.
I've already got listeners set up and I can do something like:
function handleConnectionChange(connectionState, /* ... */, doReconnect) {
if (connectionState === state.disconnected && doReconnect) {
pluginObject.connectDevice(deviceKey, backingInfo);
}
}
But I'd actually like a more generic approach -- one that is reusable even with a different set of chained events.. This is simple enough in this particular instance, but even this is a little sloppy. I've added reconnect information logic to the connection event handler and I could easily see conditional chaining getting out of hand when done like this.
Are there any libraries out there that tackle this kind of workflow? I looked at jQuery's deferred stuff and it has the chaining that I want, but it doesn't really seem to fit with callbacks. Sproutcore's StateCharts look interesting, but again don't seem to have the ability to rework an already asynchronous flow.
Update: Solution Thanks to some pointers from Gustavo, I've gone with the approach of wrapping the plugin calls to make them utilize jQuery Deferred objects:
var connectDevice = function(deviceKey, backingInfo) {
var deferred = $.Deferred();
var connectHandler = function(event) {
// unregister connectHandler
if (event.connectionState === ERROR) {
showAlert("Error connecting device", event.message);
deferred.reject();
} else {
showAlert("Device connected", event.message);
deferred.resolve();
}
};
// register connectHandler
pluginObject.connectDevice(deviceKey, backingInfo);
return deferred.promise();
};
If I make a similar wrapper for disconnecting, I can now chain the method calls (or not depending on the flow):
if (forceDisconnect) {
disconnectDevice(deviceKey).done(connectDevice(deviceKey, backingType));
} else {
connectDevice(deviceKey, backingType);
}
I can also start a chain with the connectDevice, or add another function to the forceDisconnect flow, etc.