1

How do I match the results of two nested ajax calls?

An example: the first call gets a list of urls, and the nested one, for each of them, will retrieve the titles of the pages. What happens is that while I run the second call, the first one continues in background and doesn't find a match for my results. Any solution?

Code:

$.ajax({
        dataType : "json",
        url : queryUrlDoc,
        success : function(json) {
            queryResults = json.results.bindings;
            var title;
            var temp = [];
            for(var i in queryResults){
                var url = queryResults[i]['doc'].value;
                $.ajax({
                    url: url,
                    type: 'get',
                    success: function(data) {
                        title = data;
                        var item = {
                          'url' : url,
                          'title': title
                        };
                        temp.push(item);
                    } 
                 });
            }
            var titolo;
            for(var i in temp) {
                uri = temp[i]['url'];
                titolo = temp[i]['title'];
                //do stuff
            }
        },
        error: function(){
            alert("fail");
        }
    });

Unfortunately, the 2nd 'for' cycle starts even if the first one hasn't finished it's work...

tenik
  • 250
  • 1
  • 4
  • 20
  • I think the for loop is the problem try to use while instead. – Zakaria Acharki Sep 12 '15 at 15:12
  • Please see [this](http://stackoverflow.com/questions/12478565/using-an-equivalent-of-when-done-in-jquery-1-4/12622730#12622730) question. After jquery 1.5 you can use $.when().done() With previous versions, you can use my answer there – Saic Siquot Sep 12 '15 at 15:55
  • i am confused. You make 1st call to get the list of all urls, then you make X amount of calls to get data on each of those urls. Now when you say i run the 2nd one the first one hasn't finished, do you mean the data for the first one hasn't finished downloading? or do you mean the you haven't finished getting the list of all the urls. – Muhammad Umer Sep 12 '15 at 16:28
  • and why is the problem even if you get data at all links out of order, you array still gonna have all the data – Muhammad Umer Sep 12 '15 at 16:29
  • Use promises. Example below. – Liam Sep 12 '15 at 16:41

4 Answers4

3

Do you need to wait for all the ajax calls to return before you process them? If not, you could do the following:

$.ajax({
    url: queryUrlDoc,
    dataType: 'json',
    success: function(json) {
        $.each(json.results.bindings, function(i, queryResult) {
            var url = queryResult.doc.value;
            $.ajax({
                type: 'get',
                url: url,
                dataType: 'text',
                success: function(title) {
                    // Do stuff with url and title here.
                }
            });
        });
    },
    error: function() {
        alert('fail');
    }
});

If you do need to wait for all of them to return, it gets a bit more complicated. You could use the fact that $.ajax() returns a promise, and then use $.when() to execute code when all those promises have been resolved.

$.ajax({
    url: queryUrlDoc,
    dataType: 'json',
    success: function(json) {
        var urls = [];

        var promises = $.map(json.results.bindings, function(queryResult) {
            var url = queryResult.doc.value;
            urls.push();
            return $.ajax({ type: 'get', url: url, dataType: 'text' });
        });

        $.when.apply($, promises).done(function() {
            var titles = (promises.length > 1)
                ? $.map(arguments, function(a) { return a[0]; })
                : [arguments[0]];

            var items = $.map(urls, function(url, i) {
                return { url: url, title: titles[i] };
            });

            // Do stuff with items array here.
        }).fail(function() {
            alert('fail');
        });
    },
    error: function() {
        alert("fail");
    }
});

For information on how the titles array is constructed from the arguments to the callback passed to the $.when(...).done() function, see this Stackoverflow answer.


An alternative to the code above would be to create your own promises for the ajax calls using $.Deferred() so you can control the resolved data.

$.ajax({
    url: queryUrlDoc,
    dataType: 'json',
    success: function(json) {
        var promises = $.map(json.results.bindings, function(queryResult) {
            var url = queryResult.doc.value;
            return $.Deferred(function(deferred) {
                $.ajax({
                    type: 'get',
                    url: url,
                    dataType: 'text',
                    success: function(title) {
                        deferred.resolve({ url: url, title: title });
                    },
                    error: function(jqXHR, textStatus, errorThrown) {
                        deferred.reject([jqXHR, textStatus, errorThrown]);
                    }
                });
            }).promise();
        });

        $.when.apply($, promises).done(function() {
            var items = $.makeArray(arguments);

            // Do stuff with items array here.
        }).fail(function() {
            alert('fail');
        });
    },
    error: function() {
        alert('fail');
    }
});
Community
  • 1
  • 1
John S
  • 21,212
  • 8
  • 46
  • 56
  • Excellent answer about Promises. But why do you use `$.when.apply($, promises)` instead of `$.when(promises)` ? – pomeh Sep 12 '15 at 16:55
  • @pomeh - Your question [seems familiar](http://stackoverflow.com/a/15621473/859640). :) The `$.when()` function does not accept an array, but it accepts multiple promise arguments. Using `.apply()` is a common JavaScript idiom in that case to call such a function with an array. – John S Sep 12 '15 at 17:58
  • haha that's fun to see you asking for the same thing 2 years ago :) I was confusing `apply` with `call` methods... Thanks for the explanation, that totally make sense :) – pomeh Sep 14 '15 at 20:15
1

You have 2 problems here: design and asynchrony.

Firstly, why do you need to do an AJAX call for each element returned by the first AJAX request ? If the first request sends 100 elements, then you will do 100 simultaneous AJAX calls, and just kill your browser, not good. I think you should try to reduce the amount of AJAX request done, and maybe retrieve all required infos within the first AJAX request. The less you do, the better it is (for the browser, so for the user, and also for the server, so for you).

Secondly, you have asynchrony problems. AJAX call are asynchronous. So in your current code, you're using the temp variable before it gets any element. Look at this, I've simplified the code and added some console.log statement:

$.ajax({
    dataType : "json",
    url : queryUrlDoc,
    success : function(json) {
        console.log('First AJAX call is finished');

        var queryResults = json.results.bindings;
        var temp = [];

        for(var i in queryResults)
        {
            var url = queryResults[i]['doc'].value;

            $.ajax({
                url: url,
                type: 'get',
                success: function(data) {
                    console.log('Second AJAX call is finished, with data: ' + data);

                    temp.push({
                      'url' : url,
                      'title': data
                    });

                    console.log('temp array now contains ' + temp.length + ' elements');
                }
             });
        }

        console.log('iterating over temp array, it contains ' + temp.length + ' elements');

        for(var i in temp)
        {
            uri = temp[i]['url'];
        }

    }
});

Can you guess what output you will get from this ? I bet you will get something like this:

First AJAX call is finished
iterating over temp array, it contains 0 elements
Second AJAX call is finished, with data: <data from first subrequest>
temp array now contains 1 elements
Second AJAX call is finished, with data: <data from second subrequest>
temp array now contains 2 elements

See what's going on ? The for loop is executed before the success callback, because AJAX calls are asynchronous. In order to do what you want, you should use something like Promises (see John's answer for more explanation).

pomeh
  • 4,742
  • 4
  • 23
  • 44
  • Good explanation of the problem. As for the `n + 1` ajax calls, you are correct that it would be best to avoid that, but the OP may not be in control of the service being called. – John S Sep 12 '15 at 18:05
  • Thanks :) I prefer to explain what is the problem in the first place instead of simply giving the solution, so next time the OP can avoid the same problem ;) Maybe the `n + 1` cannot be avoided but it worth mentioning it, because I really think this is a bad design which can lead to a bad user experience (among other problems) which is really unfortunate – pomeh Sep 14 '15 at 20:25
0

Try to use while, something like that will send the next request just after success of previous :

var next = false;
var i = 0;

while(next){
    next = false;

    var url = queryResults[i]['doc'].value;

    $.ajax({
        url: url,
        type: 'get',
        success: function(data) {

            title = data;
            var item = {
                'url' : url,
                'title': title
            };
            temp.push(item);

            next=true;
            i++;
        } 
    });
}

Hope this helps.

Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
0

You should use jQuery promises when calling multiple dependent AJAX calls.

jQuery.when()

jQuery deferred

Here's an example:

var firstPromise = $.ajax("http://example.com/first");
var secondPromise = $.ajax("http://example.com/second");

$.when(firstPromise, secondPromise).done(function(firstData, secondData) {
  // do something
});
Liam
  • 2,837
  • 23
  • 36