Search code examples
ajaxjquery-deferredabort

Abort chain of AJAX requests with jQuery deferred


I am making a chain of AJAX calls like in the example below, which I found at http://www.dotnetcurry.com/jquery/1022/jquery-ajax-deferred-promises.

How can I adjust the code to prevent that function B gets called if the response from the AJAX call in function A is an empty JSON object array (i.e. "[ ]")? Ideally I would like to not only abort the chain of AJAX calls, but also inform the user that no result was found.

Thanks!

function A() {
    writeMessage("Calling Function A");
    return $.ajax({
        url: "/scripts/S9/1.json",
        type: "GET",                    
        dataType: "json"
    });
}


function B(resultFromA) {
    writeMessage("In Function B. Result From A = " + resultFromA.data);
    return $.ajax({
        url: "/scripts/S9/2.json",
        type: "GET",
        dataType: "json"
    });
}


function C(resultFromB) {
    writeMessage("In Function C. Result From B =  " + resultFromB.data);
    return $.ajax({
        url: "/scripts/S9/3.json",
        type: "GET",
        dataType: "json"
    });
}

function D(resultFromC) {
    writeMessage("In Function D. Result From C = " + resultFromC.data);
}

A().then(B).then(C).then(D);

function writeMessage(msg) {
    $("#para").append(msg + "<br>");                 
}

Solution

  • Generically, you could make every one of your worker functions A, B, C, D return an object that contains the response data and whether you want to continue calling the next function in the chain, whatever that may be. Something like this:

    function A(input) {
        return $.get("/scripts/S9/1.json").then(function (response) {
            return {
                continue: response.data && response.data.length,
                data: response
            };
        });
    }
    

    Now you can make a promise chain by reducing an array of worker functions, and deciding in every step whether you want to continue (in which case you execute the next worker) or not (in which you simply keep returning the last valid result for the rest of the chain). I've wrapped this logic into a function breakableChain, for lack of a better name.

    function maybe() { return Math.random() > 0.25; }
    function A(input) { console.log('in A:', input.data); return {continue: maybe(), data: 'result from A'}; } 
    function B(input) { console.log('in B:', input.data); return {continue: maybe(), data: 'result from B'}; } 
    function C(input) { console.log('in C:', input.data); return {continue: maybe(), data: 'result from C'}; } 
    function D(input) { console.log('in D:', input.data); return {continue: maybe(), data: 'result from D'}; } 
    
    function breakableChain(workers, init) {
        return workers.reduce(function (current, next) {
            return current.then(function (result) {
                return result.continue ? next(result) : result;
            });
        }, $.Deferred().resolve({continue: true, data: init}));
    }
    
    breakableChain([A, B, C, D], 'initial data').then(function (result) {
        console.log('overall:', result.data);
    });
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

    Of course you can make a non-generic version of this that doesn't need a continue flag, but instead hard-codes the assumptions about each result directly into the if statement inside .reduce(). That would be shorter, but not re-usable.

    The whole {continue: true, data: result} thing is just a convention. Your convention could also be "if the previous call returned anything, then continue, otherwise stop", the approach would be the same, but it would become impossible to return an overall result.