10basetom 10basetom - 7 months ago 14
Javascript Question

How to use jQuery Deferred/Promise with a second $.ajax() after a timeout

I have an event handler binded to

$someEl
that should only execute when
oRes
is populated with data, which can come from either the primary source (
getData1()
) or the backup source (
getData2()
). Data is requested from the backup source only if the primary source times out.

If the primary source does not time out, then everything works; however, when the backup
getData2()
function is called,
dfd
is never resolved, so nothing ever logs when I click on
$someEl
. I suspect it's not working because the Deferred object in
getData2()
is overwriting the
dfd
variable that the
$someEl
click handler is referring to.

I have a feeling I'm not applying Deferred/Promise using a "best practice" pattern. Given this scenario, how would you make the click handler properly wait for oRes to be populated from either a primary AJAX response or a secondary AJAX response after a timeout?

Some notes for clarification:


  • getData1()
    has to be executed only after the document is ready

  • $someEl.click()
    may fire anytime during the document's loading, so the event handler needs to be defined outside of
    $(document).ready()

  • I'm stuck with jQuery 1.7.1



Here is the code:

var oRes, dfd;

// Get data from primary source
function getData1() {
dfd = $.ajax({
...
success: function(data) {
oRes = data;
},
error: function(jqXHR, textStatus, errorThrown) {
if (textStatus==='timeout') getData2();
},
timeout: 10000 // 10-second timeout
});
}

// Get data from backup source
function getData2() {
dfd = $.ajax({...});
}

$someEl.click(function() {
dfd.done(function() {
console.log('This should only log when oRes is ready');
});
});

$(document).ready(function() {
getData1();
});


I've simulated my situation in this pen: http://codepen.io/thdoan/pen/pyVyKj

Essentially, I'm having trouble getting the event handler to output "Data is ready!" when
oRes
is populated without having to manually click on the box after the page is loaded.

Answer

I think you just need to bring it up one level.

Create a Deferred up-front that your click handler will listen to (and that instance won't change).

You then resolve that deferred from either getData1() or getData2():

var oRes,

    // Resolve this deferred when data has been successfully loaded:
    dfd = $.Deferred(),

     // But add listeners to this promise:
    dfdPromise = dfd.promise();

// Get data from primary source
function getData1() {
    $.ajax({
        ...
        success: function(data) {
            oRes = data;

            // Resolve when data loaded:
            dfd.resolve(data);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            if (textStatus==='timeout') getData2();
        },
        timeout: 10000 // 10-second timeout
    });
}

// Get data from backup source
function getData2() {
    $.ajax({...})
        .then(function(data){
            // Fallback data resolve:
            dfd.resolve(data);
        });
}

$someEl.click(function() {
    dfdPromise.done(function() {
        console.log('This should only log when oRes is ready');
    });
});

$(document).ready(function() {
    getData1();
});