开发者

jquery defer and asynchronous ajax requests. - race condition?

I'm stuck trying to get my ajax requests to guarantee a response from my loaded templates.

Basically if I run my code to return my deferred object the $.then() calls before I have my template object. This only happens on the first run.

I'm tearing my hair out with this one!

My ajax call:

var ajax = {
    getTemplate: (function (id) {
    /// <summary>
    ///     This method when used with $.Deferred fetches a html string 
    ////    containing the template appliciable 
    ///     to the the supplied id.
    ///     The ajax request object is cached upon the first request and is
    ///     returned by the cache 
    ///     upon each subsequent request.
    /// </summary>
    /// <param name="id" type="String">
    ///     The id that matches the filename to request the template object
    ///     from.
    /// </param>
    /// <returns type="jqXHR">
    ///      A superset of the XMLHTTPRequest object that impliments the
    ///      promise interface.
    /// </param>

    var cache = {}; // private cache

    // Gets assigned to getTemplate.
    return function (id) {

    var url = "/templates/" + id + ".tpl";
    return cache[id]|| $.ajax({
           url: url,
           dataType: "html",
           success: function (data) {

               //ajax.getTemplate(id, da开发者_运维知识库ta);
               cache[id] = data;
           },
           error: function (XMLHttpRequest, textStatus, errorThrown) {
               log("template request " + id, 
                   XMLHttpRequest, 
                   textStatus, 
                   errorThrown);
           }
           });
        };
    } ())
};

I'm calling this on in my method like this:

$.when(ajax.getTemplate("tooltip-attributes")()).then(function (template) {

    if (template) {
       // Do my template stuff here.
    }

 });


I sometimes like to wrap ajax calls with my own deferred because I don't want to expose the ajax deferred and instead just resolve it with some custom object based on whatever the ajax call returned. For example:

function myFunction(resolved) {
    var deferred = $.Deferred();
    if ($.IsNullOrEmpty(resolved)) {
        deferred.reject("Parameters are required.");
    } else {
        $.ajax({
            type: "POST",
            url: "/something/somewhere",
            data: { someParameter : "someValue" },
            dataType: "json"
        }).done(function (result) {
            if (result.success === false) {
                deferred.reject({
                    success: false,
                    reason: "you fail"
                });
            } else {
                deferred.resolve({
                    success: true,
                    result: result.someData
                });
            }
        });
    }
    return deferred.promise();
}

Then I'll create another deferred promise to resolve it:

var deferred = $.Deferred(),
    promise = deferred.promise();

promise
    .then(myFunction)
    .then(someOtherDependentFunction)
    .fail(function (response) {
        // do fail stuff
    })
    .done(function () {
        // do done stuff
    });

You could use "when" here as well for async, instead of chaining them like that.

Then just resolve the thing

deferred.resolve({
    member: item
});

I like this because it allows me to build a dynamic function chain by making someOtherDependentFunction for example to be any other function that uses the resolved data and returns the promise. Obviously there are a lot of other ways one could use this.


Looks like you aren't creating your Deferred correctly.

try changing the top function to

// Gets assigned to getTemplate.
return $.Deferred(function (id) {


You should take a look at this tutorial, especially the section "Defer your Deferreds". This documentation about the jQuery.pipe() function is interesting too!

Thing is that every function called within $.when() need to call its promise() function in order the $.when() to wait for the completion of each function. Apparently returning an object from cache does not resolve the deferred!

Start off by letting your function always return a $.Deferred object like BonyT already described AND let it call the promise() function:

getTemplate: (function(id) {
    return $.Deferred(function (id) {...}
    .promise();
})()

Your inner return provides either a cached object that is already existing OR a $.ajax xmlhttp request. The latter always returns a Deferred object calling the .resolve() function in its .done() callback handler function - but the cache version doesn't! So you have to manually call the $.Deferred.resolve() OR $.Deferred.reject() in case of extracting your data from the local cache object.

getTemplate: (function(id) {
    if (cache.hasOwnProperty(id)) {
        var dfd = $.Deferred();
        var filtered = dfd.pipe(function(value) {
            return cache[value];
        });
        return dfd.resolve(id);
    } else {
        $.ajax({
            url: url,
            dataType: "html"
        })
        .done(function(result) {
            /*fires when resolved*/
            return result;
        })
        .fail(/*fires only when rejected*/)
        .always(/*fires when both rejected or resolved*/);
    }
})()

By the way don't use .success() and .error() any more since they will be deprecated in jQuery 1.8 "Deprecation notice". Use .done(), .fail() and .always() instead.


Probably what's confusing you is having an outer id and an inner id ... neither of which gets assigned.

The outer id can actually disappear - it would only be assigned if you passed an id in the self-executor call.

Now, the actual fix is to call the inner function (the one returned by ajax.getTemplate()) properly :

$.when(ajax.getTemplate()("tooltip-attributes")).then(...);

Note that "tooltip-attributes" now appears in the second set of parentheses. But there's a good reason not to call it this way - namely that every ajax.getTemplate() call would have its own, independent cache, not one common cache. It would make more sense to do as follows :

var templateGetter = ajax.getTemplate();//Assign the returned function to make it reusable
...
$.when(templateGetter("tooltip-attributes")).then(...);

Personally, I find the whole construction confusing and would probably use a Crockford-style module pattern as follows:

var AJAX = {
    var cache = {}; // private cache of jqXHR objects (promises)
    var getTemplate = function (id) {
        var url = "/templates/" + id + ".tpl";
        if(!cache[id]) {
            cache[id] = $.ajax({
                url: url,
                dataType: 'html',
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    log("template request " + id, XMLHttpRequest, textStatus, errorThrown);
                }
            });
        }
        return cache[id];
    };
    return {
        getTemplate: getTemplate
    }
};

Now, AJAX.getTemplate() is guaranteed to return a promise.

AJAX.getTemplate("tooltip-attributes").then(function (template) {
    if (template) {
        // Do my template stuff here.
    }
});
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜