Introduction
Cascading Asynchronous Function Execution (CAFÉ) is a design pattern for easily managing the execution sequence of asynchronous requests and callbacks within a large JavaScript framework.
Background
On a recent JavaScript framework I was building for custom web approval forms based on MVVM, the form initialization required many asynchronous requests to various web services within the company to gather data including the user’s profile, permissions, group memberships, and workflow history. The requests needed to happen in a specific order since each function relied on data from a previously executed function. All of the requests needed to execute asynchronously for optimal user experience.
After several revisions to the framework, I needed to rearrange the execution order of all of these asynchronous requests and add new ones. This quickly became a problem when tracking down the execution sequence and the callback function calls within each.
This problem compelled me to come up with a simple approach to easily rearrange the execution sequence and management of all asynchronous requests, and callbacks – all in one controlling function near the beginning of the framework code. I call this approach Cascading Asynchronous Function Execution or simply, CAFÉ.
Using the code
Create an instance of Cafe to execute your asynchronous requests as shown below.
function Cafe(args){
this.fns = args.fns;
this.statusList = document.getElementById(args.statusListId);
};
Cafe.prototype = {
cascade: function(){
if(arguments[0]){
this.updateStatus(arguments[0]);
}
if(this.fns.length == 0){ return; }
this.fns.shift()(this, arguments);
}
, ajaxSim: function(args){
setTimeout(function(){
args.success();
}, 2000);
}
, updateStatus: function(str){
var li = document.createElement("li");
li.innerHTML = str;
this.statusList.appendChild(li);
}
};
(function(){
var cafe = new Cafe({
fns: [fn1, fn2, fn3, fn4, fn5],
statusListId: "StatusList"
});
cafe.cascade('Executing asynchronous cascade...');
function fn1(){
cafe.ajaxSim({
success: function(){
cafe.cascade("Fn1 complete.");
}
});
};
function fn2(){
var args = arguments;
cafe.ajaxSim({
success: function(){
cafe.cascade("Fn2 complete.");
}
});
};
function fn3(){
cafe.ajaxSim({
success: function(){
cafe.cascade("Fn3 complete.");
}
});
};
function fn4(){
cafe.ajaxSim({
success: function(){
cafe.cascade("Fn4 complete.");
}
});
};
function fn5(){
cafe.ajaxSim({
success: function(){
cafe.cascade("Fn5 complete. All asynchronous operations complete.");
}
});
};
})();
Other Ideas for Your Implementation
Since the functions are referenced in an array, your approach could be developed further to dynamically change the order of execution or add/remove functions based on any number of circumstances in your implementation using the Array object methods including slice, reverse, sort, push, pop, and shift.