The crux of the problem is clearly the relationship between B and C, which, in summary, appears to be :
- If B,
pull-C().then(pull-B)
;
- If C,
pull-B().then(pull-C)
;
In the current attempt, you are running into problems by trying to code the flow logic inside pull-B()
and pull-C()
, which is ultimately possible but complex.
A simpler strategy is to make the pull_X()
functions very simple promise-returning data retrievers, and to code the flow logic and data cleanups inside the switch/case structure in .init()
. You will see what I mean in the code below.
Apart from being simpler, this will also avoid any chance of circular dependencies between pull_B()
and pull_C()
.
By fully exploiting promises, you will also find that :
- the need for
this.dfd
disappears (in favour of returning promises from functions).
- the need for
this.data
disappears (in favour of allowing promises to deliver the data).
- the need for a callback to be passed to
.pull()
disappears (in favour of chaining .then()
in the caller). Thus, callback hell disappears.
Try this :
(function($) {
"use strict";
/**
* The Thing
*
* @constructor
*/
function Thing( ) {
/* properties */
this.url = [
/* path */,
/* id */
];
}
Thing.prototype.pull = function(url, args, type) {
return $.ajax({
type: type || 'GET',
url: foo.root + url,
data: $.extend({}, args || {}),
dataType: 'json'
});
};
Thing.prototype.pull_As = function() {
return this.pull('a', this.query);
};
Thing.prototype.pull_A = function() {
this.nav = false;
return this.pull('a/'+ this.url[2]);
};
Thing.prototype.pull_B = function() {
return this.pull('b/' + this.url[2]);
};
Thing.prototype.pull_C = function(id) {
return this.pull('c/' + id || this.url[2]);
};
Thing.prototype.pull_D = function() {
return this.pull_As();
};
Thing.prototype.render = function(data) {
var i, len, html,
that = foo.thing, /* because 'this' is the promise object */
title = document.title.split('|');
for (i = 0, len = title.length; i < len; i += 1) {
title[i] = $.trim(title[i]);
}
title[0] = $.trim(that.title);
document.title = title.join(' | ');
html = Hogan.wrapper.render({
'data': data,
});
$('#thing_wrapper').empty().append(html);
};
Thing.prototype.init = function( ) {
var promise,
that = this;
switch (this.url[1].toLowerCase( )) {
case 'a':
promise = this.pull_A().then(function(data_A) {
/* ... do A data cleanup */
return data_A;//will be passed through to .render()
});
break;
case 'b':
promise = this.pull_C().then(function(data_C) {
//Here an inner promise chain is formed, allowing data_C, as well as data_B, to be accessed by the innermost function.
return that.pull_B().then(function(data_B) {
var data = ...;//some merge of data_B and data_C
return data;//will be passed through to .render()
});
});
break;
case 'c':
var id = ???;
promise = this.pull_C(id).then(function(data_C) {
/* ... do C data cleanup */
return data_C;//will be passed through to .render()
});
break;
case '':
default:
promise = this.pull_D().then(function(data_D) {
/* ... do D data cleanup */
return data_D;//will be passed through to .render()
});
}
promise.then(this.render, console.error.bind(console));
};
window.Thing = Thing;
})(jQuery);
Note in particular that a promise or data is returned from the various functions.
I doubt my attempt is 100% correct. The overall structure should be fine, though you will need to take a close look at the detail. I may have misunderstood the B/C dependencies. With luck, it will be simpler than what I have coded.
Edit: code amended in light of comments below.