1

I've tried going through this question and also this, but I can't seem to figure out how to make my requirements work.

I want to call https://reqres.in/api/users a number of times over a loop. This AJAX call returns only the first page of dummy users. After I get the first page, I want to call the next pages.

Here's my code :

$(document).ready(function() {

    function getMoreUsers() {
        var def = $.Deferred();
        var requests = [];
        for (var j=2; j<=4; j++) {
            console.log("getting info for page # " + j);
            requests.push(
                $.ajax("https://reqres.in/api/users?page=" + j).done(function() {
                    console.log("got info for page # " + j);
                    def.resolve();
                })
            );
        }
        return def.promise();
    }   

    function getAllUsers() {
        var def = $.Deferred();
        var requests = [];
        for (var i=0; i< 2; i++) {
            console.log("iteration # " + i);
            requests.push(
                $.ajax("https://reqres.in/api/users").done(function(data) {
                    console.log("got first page info");
                    getMoreUsers();
                    def.resolve();
                })
            );
        }
        return def.promise();
    }

    getAllUsers().done(function() {
        console.log("all completed");
    });
});

The output that I get is this :

iteration # 0 
iteration # 1 
got first page info 
getting info for page # 2 
getting info for page # 3 
getting info for page # 4 
all completed 
got first page info 
getting info for page # 2 
getting info for page # 3 
getting info for page # 4 
got info for page # 5

However, I want this :

iteration # 0
got first page info
getting info for page # 2
got info for page # 2
getting info for page # 3
got info for page # 3
getting info for page # 4
got info for page # 4
iteration # 1
got first page info
getting info for page # 2
got info for page # 2
getting info for page # 3
got info for page # 3
getting info for page # 4
got info for page # 4
all completed

I don't even understand how page # 5 came in the output when I'm looping till 4, and it came 6 times, like below :

enter image description here

Sidharth Samant
  • 714
  • 8
  • 28
  • jQuery.Deferred() is what you need – Sampgun Jun 13 '17 at 12:36
  • I have used it, but not properly, I guess. – Sidharth Samant Jun 13 '17 at 12:37
  • First of all, you don't need to create a new deferred object, resolve and return it for each AJAX request. The `$.ajax` method itself returns a deferred object :) – Terry Jun 13 '17 at 12:55
  • The main problem was putting the ajax calls into a for loop. The foor loop doesn't wait for the calls to be resolved. Anyway check my solution...if you want to use the _total_pages_ property tell me... – Sampgun Jun 13 '17 at 13:00
  • Your question seems a bit counterintuitive to me: AJAX requests are meant to be asynchronous, meaning that you cannot really dictate what happens in what order. Is there a reason why you want iteration no. 2 to start only after iteration no. 1? In other words, why does the order matter? Is there a user-interaction event required? Otherwise you simply fetch everything, and listen to the final promise. – Terry Jun 13 '17 at 13:05
  • You got the point. I think Sidhart knows he might handle a large amount of users in the future..Even though I think this would make sense if users were displayed in pages and the next call is triggered by user interaction (es. next page)... – Sampgun Jun 13 '17 at 13:14
  • @Terry there is a reason for the order. This is just a dummy example. For my real project, the order of the calls matter. – Sidharth Samant Jun 13 '17 at 13:14

2 Answers2

1

On a second thougth you don't need the deferred. Recursion is better.

            $(document).ready(function () {

                var apiUrl = "https://reqres.in/api/users";
                var baseAjaxConfig = {
                    method: "GET",
                    url: apiUrl
                };
                var page = 1;
                var maxUsers = 5; //CHANGE THIS ACCORDING TO WHAT YOU WANT
                function getUser(page) {
                    var ajaxConfig = $.extend({}, baseAjaxConfig, {data: {page: page}});
                    $.ajax(ajaxConfig).done(function () {
                        (page < maxUsers) && getUser(page+1);
                    }).fail(function () {
                        (page < maxUsers) && getUser(page+1);
                    });

                }

                getUser(page);
            });

here's a fiddle ==> https://jsfiddle.net/tonysamperi/5j8166be/

Sampgun
  • 2,822
  • 1
  • 21
  • 38
1

Why not keep it simple?

var getUsers = function(i) {
    $.ajax("https://reqres.in/api/users?page=" + i).done(function() {
        if (i < 5) {
            getUsers(i + 1);
        }else{
            //done!
        }
    });
}    
getUsers(1);

Update:

Thanks, recursion does seem to work, but if I attach a done() to getUsers() like so - getUsers(1).done(function() { console.log("all done");}); it doesn't fire. I don't understand. I thought $.ajax() returned a deferred object on its own.

my code was just a hint how can you resolve your issue. anyways let me help you futher.

there is simple way:

    $.ajax("https://reqres.in/api/users?page=" + i).done(function() {
        // use data, store it in array outside or draw HTML
        if (i < 5) {
            getUsers(i + 1);
        }else{
            //done! do something when finished
            // iAmDoneHere()
        }
    });

but if you want to use deferred: so $.ajax returns the Deferred. Recursion works well but I guess you want to exectule final "downloaded all!" function. In such case you need to improve code a bit.

var pages = [];
var getUsers = function(maxPage, currentPage, deferred) {
    var deferred = false;
    if (!currentPage) {
        // this is the top function call
        // the top call without recursion
        var currentPage = 1;
        deferred = $.Deferred();
    }
    $.ajax(..."?page="+currentPage).done(function(){
        // we got page info, great! what next?
        pages.push({insert the page data here});

        // what next?

        // if there is more to fetch, do it
        if (i < maxPage) {
            // pass maxPage, page to parse + 1 and top deferred
            var subd = getUsers(maxPage, i + 1, deferred);
        }else{
            // if there is more to parse, do it
            // we downloaded the final page
            // so now we can finally resolve the top deferred
            // which was passed in every recursion
            deferred.resolve(); 
        }
    }
    return deferred;
}    

getUsers(10).done(function(){
   // executed when all pages are done
   // results are stored in pages[] 
});

the worst part is I wrote already a lot and this still could be improved (i should pages[] variable as global/parent scope)

i want to say managing asynchronous callbacks is really easy but it's more advanced that making a simple callback.

if you work on bigger project you'll propably write or use some class that will do all of it for you without worrying about anything for example

var getUsers = function(maxPages) {
  var d = $.Deferred();
  var pages = [];
  var queue = new Queue();
  for (var i=0;i<maxPages;i++) {
    queue.push(function(page){
      return $.ajax(.."?page="+page).done(function(){pages.push(page);});
    }, i);
  }
  queue.run(function(){
    d.resolve(pages);
  });
  return d;
}
getUsers(10).done(function(pages){/* all pages in "pages" */});

this is done the right way, and you won't repeat your code if you will want to use queue in other place. also there ale plenty ready npm packages out there

also I need to mention I can see you really want to stick to deferred white lot of people just use callbacks instead deferred or promises for simple tasks.

// Deferred way
function doJob(arg1, arg2) {
   var d = $.Deferred();
   window.setTimeout(function(){
      d.resolve();
   }, 100);
   return d;
}    

// Callback way
function doJob(arg1, arg2, callback) {
   window.setTimeout(function(){
      callback();
   }, 100);
}

which save a code a bit and complexity but offers less layers and options for developer. Both methods are fine. I am saying all of this to let you know there are many methods and there is no definitve answer how to answer your question.

I would go with some Queue, the callback solution is the simplest, the Deferred/Promise + recursion solution is OK.

Peter
  • 16,453
  • 8
  • 51
  • 77
  • Thanks, recursion does seem to work, but if I attach a `done()` to `getUsers()` like so - `getUsers(1).done(function() { console.log("all done");});` it doesn't fire. I don't understand. I thought `$.ajax()` returned a deferred object on its own. – Sidharth Samant Jun 14 '17 at 15:24