When ever completion functions or callbacks get nested too deep or code is getting repeated over and over again, I tend to think about a data table solution with a common function:
function fadeSequence(list) {
var index = 0;
function next() {
if (index < list.length) {
$(list[index++]).fadeOut(next);
}
next();
}
var fades = ["div1", "div2", "div3", "div4", "div5"];
fadeSequence(fades);
And, if you wanted a different type of animation for some of the items, you could create a array of objects that describe what each successive animation is supposed to be. You could put as much detail in the array of objects as was needed. You can even intermix animations with other synchronous jQuery method calls like this:
function runSequence(list) {
var index = 0;
function next() {
var item, obj, args;
if (index < list.length) {
item = list[index++];
obj = $(item.sel);
args = item.args.slice(0);
if (item.sync) {
obj[item.type].apply(obj, args);
setTimeout(next, 1);
} else {
args.push(next);
obj[item.type].apply(obj, args);
}
}
}
next();
}
// sequence of animation commands to run, one after the other
var commands = [
{sel: "#div2", type: "animate", args: [{ width: 300}, 1000]},
{sel: "#div2", type: "animate", args: [{ width: 25}, 1000]},
{sel: "#div2", type: "fadeOut", args: ["slow"]},
{sel: "#div3", type: "animate", args: [{ height: 300}, 1000]},
{sel: "#div3", type: "animate", args: [{ height: 25}, 1000]},
{sel: "#div3", type: "fadeOut", args: ["slow"]},
{sel: "#div4", type: "fadeOut", args: ["slow"]},
{sel: "#div1", type: "fadeOut", args: ["slow"]},
{sel: "#div5", type: "css", args: ["position", "absolute"], sync: true},
{sel: "#div5", type: "animate", args: [{ top: 500}, 1000]}
];
runSequence(commands);
And, here's a working demo of this second option: http://jsfiddle.net/jfriend00/PEVEh/