0

We have an AJAX web application, and a common pattern we have is a number of ajax calls that call one after the other. This is common when we save something with multiple attached entities. An example might be saveCustomer() --> saveCustomerAddress() --> saveCustomersOrder().

We currently have it so that when methodA() succeeds, ie the first method, it calls methodB(), and so forth (See code below). The disadvantage to this pattern is that it is really hard to see what is going on. If you read methodA() you have no idea from reading the method name that it also calls methodB() and methodC(). The other disadvantage is that if you want to change the chaining you have to rewrite a lot of code, and if you want to just call a method individually, it can't be done because it will call methods downstream.

function Tester() {
    this.url = 'https://public.opencpu.org/ocpu/library/';

    this.save = function() {
        this.methodA();
    }

    this.methodA = function () {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {

            //check for errors... and if OK
            alert('A OK');
            self.methodB();

        })
    }
    this.methodB = function () {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {

            //check for errors... and if OK
            alert('B OK');
            self.methodC();

        })
    }
    this.methodC = function () {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {
            //OK
            alert('C OK');
        })
    }
}
new Tester().save();

I am scratching my head trying to figure out a better pattern. I figured you could wrap up the later methods in callback functions, and then somehow pass them through each method but I'm not really sure how to approach this.

Is anyone aware of a common type of pattern where you can remove the method dependencies when chaining methods?

Adrian Forsius
  • 1,437
  • 2
  • 19
  • 29
Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225
  • 3
    Use Promises. jQuery has it, or any other library. Take a look here: http://stackoverflow.com/questions/11539605/chain-of-jquery-promises or here http://www.dotnetcurry.com/showarticle.aspx?ID=1022 – Christian Benseler Sep 10 '14 at 13:32
  • Or, less advanced, just use callbacks… – Bergi Sep 10 '14 at 13:41

4 Answers4

3
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>");                 
}
Rotka
  • 430
  • 2
  • 11
  • how is this different to the other answer? You are just returning from the ajax call right? So does that prevent it from being asynch? – Oliver Watkins Sep 10 '14 at 14:29
  • 1
    jQuery Doc:Callbacks are executed in the order they were added. Since deferred.then returns a Promise, other methods of the Promise object can be chained to this one, including additional .then() methods. And that’s what we are doing here. Every Ajax method of jQuery already returns a promise. When the Ajax call in function A completes, it resolves the promise. The function B() is then called with the results of the Ajax call as its first argument. When the ajax call in B() completes, it resolves the promise and function C() is called with the results of that call and so on. – Rotka Sep 10 '14 at 14:36
  • got it. I changed my code to fit his solution here : http://jsfiddle.net/h8tfrvy4/2/ – Oliver Watkins Sep 10 '14 at 14:53
1

You can use dynamic calling like this:

        this.reqChain = ['req_1', 'req_2'];

        this.callChain = function(){
            if(!self.reqChain.length) return;
            ajax({
                url: self.reqChain[0],
                async: true,        
                always: function(processedDataOrXHRWrapper, textStatus, whrWrapperOrErrorThrown){
                   self.reqChain.shift();
                   $(document).trigger('request_'+self.reqChain[0]+'_callback', [processedDataOrXHRWrapper, textStatus, whrWrapperOrErrorThrown])
                   self.callChain();
                }
           });
    }

You can pass callback or you can bind to the dynamic event.

n_ilievski
  • 74
  • 4
1

Here's my answer to my own question. I am wrapping all my methods in functions, and passing through a callback into my methods.

It seems to do the job. Does anyone have any comments?

function Tester2() {

    this.save = function() {

        var self = this;

        var callbackC = function() {
            self.methodC();
        }

        var callbackB = function() {
            self.methodB(callbackC);
        }

        this.methodA(callbackB);

    }

    this.methodA = function (callbackFn) {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {


            //check for errors... and if OK
            alert('A OK');
            if (callbackFn)
                callbackFn();

        })
    }
    this.methodB = function (callbackFn) {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {


            //check for errors... and if OK
            alert('B OK');
            if (callbackFn)
                callbackFn();

        })
    }
    this.methodC = function () {
        var self = this;

        $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {
            //OK
            alert('C OK');
        })
    }
}

new Tester2().save();
Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225
  • Yes, that's totally fine (what I meant with my comment "*Or, less advanced, just use callbacks…*") - apart from that `.always` ignores errors. It might be easier to understand if you are more accustomed to callbacks than to promises. The accepted answer by @Rotka is just more concise, promises are considered more elegant. – Bergi Sep 10 '14 at 16:05
0

Try this:

function Tester() {
    var self = this;
    this.url = 'https://public.opencpu.org/ocpu/library/';

    this.save = function() {
        self.methodA().then( self.methodB() ).then( self.methodC() )
    }

    this.methodA = function () {
        var self = this;

        return $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {

            //check for errors... and if OK
            alert('A OK');


        })
    }
    this.methodB = function () {
        var self = this;

        return $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {

            //check for errors... and if OK
            alert('B OK');


        })
    }
    this.methodC = function () {
        var self = this;

        return $.ajax({
            url: self.url,
            async: true
        }).always(function (processedDataOrXHRWrapper, textStatus, xhrWrapperOrErrorThrown) {
            //OK

        })
    }
}
new Tester().save();
Christian Benseler
  • 7,907
  • 8
  • 40
  • 71
  • To elaborate: this is using Promises, which are built into jQuery's Ajax implementation. More information can be found in the jQuery docs. – ajm Sep 10 '14 at 13:39
  • But it's using them wrong. You need to pass a callback to `.then()`! – Bergi Sep 10 '14 at 13:40
  • i dont know why someone gave you a negative vote. This seems to work fine. – Oliver Watkins Sep 10 '14 at 13:41
  • No, it does not. It executes all three ajax requests in parallel, contrary to the sequential execution that you wanted. – Bergi Sep 10 '14 at 14:06
  • I don't see any difference to his answer, and the top rated answer though. – Oliver Watkins Sep 10 '14 at 14:44
  • 1
    oh, yeah i do, sorry. In his example he is EXECUTING the method ... ie using () when passing into the then() method, whereas in the other answer only the method REFERENCE is being passed in *phew* – Oliver Watkins Sep 10 '14 at 14:51