简体   繁体   中英

Nested AJAX calls to be called in a particular order

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 :

在此处输入图片说明

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/

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM