2

I don't quite understand magic deferred objects with jQuery. Assume the following code:

function callWebService(uri, filter, callback)
{
  var data = {};

  if (filter && filter != '')
    data['$filter'] = filter;

  jQuery.ajax({
    url: '/_api/lists/' + uri + '/items',
    data: data,
    success: callback,
    dataType: 'json'
  });
}

function getInitialData() {
  callWebService("InitialData", "", function (data) {
    //do stuff with data
  });
}

function getGreenData() {
  callWebService("GreenData", "filter from InitialData", function (data) {
    //do stuff with data
  });
}

function getRedData() {
  callWebService("RedData", "filter from InitialData", function (data) {
    //do stuff with data
  });
}

function getFinalData() {
  callWebService("FinalData", "filter from RedData & GreenData", function (data) {
    //do stuff with data
  });
}

The order I want to do things is like so - in the end I will call four webservices whereas the calls depend on each other (one long chain):

  1. Call getInitialData
  2. Call getGreenData with dependency on getInitialData
  3. Call getRedData with dependency on getInitialData
  4. Call getFinalData with dependencies on getGreenData and getRedData

As you can tell 2 & 3 could happen simultaneously. I'm thinking I can use jQuery.when() (or resolve?), I just don't know how to apply it here. I think I need to rework the functions to always return the ajax object?

Pseude-code would look like this:

getInitialData().then(getGreenData, getRedData).then(getFinalData)
Dennis G
  • 21,405
  • 19
  • 96
  • 133
  • 2
    Don't start function names with capital letters, it makes the colors in Stack Overflows syntax highlighting look strange ! – adeneo Dec 17 '14 at 16:39
  • If you `return` the ajax method in CallWebService you should have access to the `.then` promise – Christopher Marshall Dec 17 '14 at 16:40
  • fixed function names, came directly from my C# environment ;-) – Dennis G Dec 17 '14 at 16:45
  • Do you want the calls in parallel or sequentially as that subtlety changes my example below? Have added a parallel example to my answer. Apologies for any typos. :) – iCollect.it Ltd Dec 17 '14 at 17:04
  • Parallel & Sequential ;-) Due to your answer my best guess is like so: `getInitialData.when(getGreenData, getRedData).then(getFinalData).done(function())` - I just need to figure out what is passed to each promise, so i can do something in the `done` call. – Dennis G Dec 17 '14 at 17:12
  • First of all, deferreds are no magic. They're implemented in plain Js :-) – Bergi Dec 17 '14 at 17:30
  • @Bergi: "any sufficiently advanced technology is indistinguishable from magic" `Arthur C. Clarke` (the real question is, what is that big `1^2` * `2^2` * `3^2` monolith doing here) :) – iCollect.it Ltd Dec 17 '14 at 17:41

2 Answers2

4

$.ajax returns a jQuery promise. You can then call then on that promise to chain completion to a function. The ajax data is passed as the promise parameter to any final callback function. That is because $.ajax "promises to return the Ajax data".

If you follow the same pattern for all your functions you can chain everything as you wanted. By not calling the functions, or adding anonymous callbacks, it simply uses the resulting promises from each function call and combines them together.

Something like:

function CallWebService (uri, filter)
{
  var data = {};

  if (filter && filter != '')
    data['$filter'] = filter;

  return jQuery.ajax({
    url: '/_api/lists/' + uri + '/items',
    data: data,
    dataType: 'json'
  });
}

function getGreenData() {
  return CallWebService("GreenData", "filter from InitialData");
}

function getRedData() {
  return CallWebService("RedData", "filter from InitialData");
}

function GetInitialData() {
    return CallWebService("InitialData", "").then(GetGreenData);
}

// Fetch green data then red data sequentially
function GetFinalData () {
    return getGreenData().then(getRedData);
}

// Call the final one
GetFinalData().done(function(greendata, reddata){
     Alert("all done!");
});

To run promises in parallel, evaluate the functions immediately and let the resulting promises combine with $.when:

e.g.

// Fetch green data and red data in parallel
function GetFinalData () {
    return $.when(getGreenData(), getRedData());
}
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • 1
    Good answer. I'm still stuck on how to pass data from one call to the next. I first want to call InitialData, then call Green & Red data with a filter coming from InitialData, then call FinalData with a filter from Green & Red. All one long chain. – Dennis G Dec 17 '14 at 18:00
  • 1
    @Dennis G: Each promise is *to return specific data*. Any data will be passed as the parameters of the receiving functions, one per promise (in the order they were chained - not completed, I believe, but I really need to create a test app). :) – iCollect.it Ltd Dec 18 '14 at 09:39
0

Hopefully this will give a better idea of how to pass data from one call to the next.

First a version of callWebService() that differs in that :

  • it doesn't accept a callback
  • it returns the jqXHR object returned by $.ajax()
function callWebService (uri, filter) {
    var data = {};
    if (filter && filter != '') {
        data.$filter = filter;
    }
    return jQuery.ajax({
        url: '/_api/lists/' + uri + '/items',
        data: data,
        dataType: 'json'
    });
}

Now your four "get..." functions, which differ in that :

  • the functions accept a filter parameter
  • the functions return a promise
  • the callback now appears as a parameter passed to a chained .then() rather than passing it to callWebService().
  • the callback does whatever is necessary with the returned data and, importantly, returns it, thus making the data available further down a promise chain wherever getInitialData(), getGreenData() etc are called.
function getInitialData (filter) {
    return callWebService("InitialData", filter).then(function (data) {
        //do stuff with initial data
        return data;
    });
}
function getGreenData (filter) {
    return callWebService("GreenData", filter).then(function (data) {
        //do stuff with green data
        return data;
    });
}
function getRedData (filter) {
    return callWebService("RedData", filter).then(function (data) {
        //do stuff with red data
        return data;
    });
}
function getFinalData (filter) {
    return callWebService("FinalData", filter).then(function (data) {
        //do stuff with final data
        return data;
    });
}

Finally the master routine that controls the sequencing and data flow.

function getAllSortsOfDependentData() {
    return getInitialData().then(function (initialData) {
        var filter1 = initialData...;//some property of initialData (this may be several lines of code)
        var filter2 = initialData...;//some other property of initialData (this may be several lines of code)
        var greenPromise = getGreenData(filter1);
        var redPromise = getRedData(filter2);
        return $.when(greenPromise, redPromise).then(function (greenData, redData) {
            var filter3 = greenData...;//some property of greenData (this may be several lines of code)
            var filter4 = redData...;//some property of redData (this may be several lines of code)
            return getFinalData(filter3, filter4).then(function(finalData) {
                //Now a summary object can be returned.
                //This is possible due to initialData/greenData/redData being accessible from closures formed by outer functions.
                return {
                    initialData: initialData,
                    greenData: greenData,
                    redData: redData,
                    finalData: finalData
                };
            });
        });
    });
}

getAllSortsOfDependentData() can now be called as follows, with the summary data available in the callback of a chained .then() :

getAllSortsOfDependentData().then(function(dataObject) {
    //Everything above is complete.
    //If required, all the fetched data is available here as properties of `dataObject`
    //dataObject.initialData
    //dataObject.greenData
    //dataObject.redData
    //dataObject.finalData
});

That's the basics. In almost every function, various refinements are possible.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44