Question

I'm currently struggling with promises and I think I got the concept a bit wrong. Basically what I am trying to do is writing a little template handler.

It has a load() method which loads a template and stores it into a property and it is going to be chainable. The method I want to chain it with is attachTo() with appends the template I loaded before to a DOM element.

Since the template is loaded asynchronously I tried to use promises. But it seems that the done() method on the promise is firing immediately, and before the async call has been finished.

I call it like that:

tpl.load('stuff.html').attachTo($('.someElement'));

What I want it to behave is, that whenever I call attachTo() it will wait for my previously called load() method to get it's asynchronous stuff done, and then fire the callback provided in the done method.

Here's the relevant part of the handler

var tpl = {
...
    template: null,
    promise: null,

    load: function(template) {
        this.promise = $.get(this.templateUrl + template, function(response){
            this.template = response;
            console.log(this.template);
            //Outputs the desired value
        });
        return this;
    },

    attachTo: function(el) {
        var self = this;
        $.when(this.promise).done(function(){
            //outputs null but should output the 
            //value that has been set in my async call
            console.log(self.template);
        });
    }

..
}

tpl.load('stuff.html').attachTo($('.someElement'));
Was it helpful?

Solution

While you have identified the problem yourself already, the proposed solution is not a good one.

You should not use global variables that are set somewhen and use promises just for propagating changes, but the promises should represent these values. This leads to a better, functional programming style.

In your case:

var tpl = {
    …
    templatePromise: null,
    load: function(template) {
        this.templatePromise = $.get(this.templateUrl + template).then(function(response) {
            console.log(this.template);
            //Outputs the desired value
            return response;
        });
        return this;
    },
    attachTo: function(el) {
        $.when(this.templatePromise).done(function(template) {
            // get the template here:              ^^^^^^^^
            console.log(template);
        });
    }
    …
}

OTHER TIPS

It turned out that it was a scoping problem. There was nothing wrong with the deferreds but with the scope of the instance I assigned the value to.

load: function(template) {
    this.promise = $.get(this.templateUrl + template, function(response){
        this.template = response;
        console.log(this.template);
        //Outputs the desired value
    });
    return this;
},

Here I assigned a value to this.template. But I was not in the scope of my object but in the scope of the closure of the $.get() method. Hence other methods cannot pull the value out of the property because it was never stored there.

I came up with:

load: function(template) {
    var self = this;
    this.promise = $.get(this.templateUrl + template, function(response){
        self.template = response;
    });
    return this;
},

I first assigned the object instance instance to the self variable and refered to it inside the closure instead of using this. To solve it more elegantly also a $.proxy() could have been used.

That's all. It was just a scoping and not a deferred issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top