0

Here is my current code: https://gist.github.com/benjamw/f6d5d682caddd4c1e506

What I'm trying to do is: based on what the URL is when the user hits the page, pull down different data from the server, and when that's all done, render the page with Hogan.

My problem is that step B needs data from step C to render properly, and step C needs data from step B to pull the right data, but step C can be pulled by itself if that's the page the user requests (the data needed by C to pull properly is part of the URL when going directly to C).

I have a Deferred being stored in Thing that gets resolved in the various pull steps and triggers the render, but in pull_B, I don't want it resolved until it gets and cleans data from both pull_B and pull_C. But if the user goes direct through C, then I want it to resolve just fine.

How can I dynamically add a promise to the when in the init() function when the process goes through the B path to include the C path?

Or, how can I make B pass it's promise into C and then resolve it there, but still keep the functionality of being able to go through C itself, and have it still resolve the main deferred object without going through B first?

I'm trying really hard not to drop into callback hell for this, but I'm finding it difficult to do so.

Benjam
  • 5,285
  • 3
  • 26
  • 36
  • Code should really be in the question, not a link. – Roamer-1888 Aug 12 '15 at 13:42
  • I've heard many times that if the code is long, to put it elsewhere and link to it. If this code were shorter, I certainly would have put it in the question. – Benjam Aug 13 '15 at 05:26

1 Answers1

1

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • In the pull_B function, it relies on data from pull_C, but it's not reciprocal. pull_C does not rely on data from pull_B and can be called by itself. This was my main issue, but using your method of pulling everything out except the ajax call and return the promise, it will probably help me. Thanks. – Benjam Aug 13 '15 at 05:29
  • There are some `this` issues inside the `then` functions. Inside those functions, `this` refers to the promise object. I'm sure you know, but for posterity, I'm noting it here. – Benjam Aug 13 '15 at 06:17
  • I have a question about the `c` case. When passing the `data` into `pull_C` after it gets passed through `pull_B`, it's the response of `pull_B`. Once inside `pull_C`, how should I got about merging the response data from `pull_C` with the data returned from `pull_B` if the return value from `pull_B` is also a promise object? I know I can do it outside of those functions, I was just wondering if there was some trick I was missing. – Benjam Aug 13 '15 at 06:20
  • Other than those questions and some minor tweaks, this worked perfectly! Thank you! – Benjam Aug 13 '15 at 07:10
  • I'm confused. From 1st comment - "pull_B relies on data from pull_C", but "pull_C does not rely on data from pull_B". From 3rd comment - "When passing the data into pull_C after it gets passed through pull_B ...". Which case is simple and which one complex? – Roamer-1888 Aug 13 '15 at 07:11
  • Sorry, trying to keep the IP safe, I've confused myself. Basically, one of the functions must use data from the other, and the other can be called by itself. Think of it like articles and users, articles must have the user data, but users can be called without the article. – Benjam Aug 13 '15 at 15:49
  • OK, I'll assume that 'b' is the complex case and 'c' is the simple one. – Roamer-1888 Aug 13 '15 at 16:04