Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery Recursive AJAX Call Promise

I'm still trying to figure out how to use jQuery deferred object in recursive AJAX call. I have a code like this

function request(page, items){    

    //building the AJAX return value for JSFiddle dummy AJAX endpoint
    var ret = {
        totalPage: 10,
        currentPage: page,
        items: []
    };
    for (var i = page; i < (page + 5); i++){
        ret.items.push(i);
    }

    //calling the AJAX
    $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        },
        success: function(data){
            if (data.currentPage <= data.totalPage){
                var filtered = data.items.filter(function(el){
                    return el % 2 == 1;
                });
                var newitems = items.concat(filtered);
                console.dir(newitems);
                request(data.currentPage + 1, newitems);
            } else {
                console.dir(items);
                //resolve all item
            }
        }
    });
}

function requestAll(){
    request(1, []);
    //should return a promise tha contains all items
}

Here's the JSFiddle http://jsfiddle.net/petrabarus/BHswy/

I know how to use promise in single AJAX call, but I have no idea how to use it in a recursive AJAX call. I want to call the requestAll function in a way similar like below

var promise = requestAll();
promise.done(function(items){
    console.dir(items);
});

How can I do this?

like image 529
Petra Barus Avatar asked Apr 29 '14 03:04

Petra Barus


2 Answers

You should not use the success parameter if you want to work with promises. Instead, you want to return a promise, and you want to use then to transform the results of a promise into something different, possibly even another promise.

function request(page) {    
    …
    // return the AJAX promise
    return $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        }
    });
}

function requestOddsFrom(page, items) {
    return request(page).then(function(data){
        if (data.currentPage > data.totalPage) {
            return items;
        } else {
            var filtered = data.items.filter(function(el){ return el%2 == 1; });
            return requestOddsFrom(data.currentPage + 1, items.concat(filtered));
        }
    });
}

function requestAll(){
    return requestOddsFrom(1, []);
}

requestAll().then(function(items) {
    console.dir(items);
});
like image 166
Bergi Avatar answered Sep 29 '22 23:09

Bergi


Since you're already sequencing the Ajax operations one after the other, without totally restructing your code, you can just use one deferred that you resolve on the last Ajax call:

function request(page, items, defer){    

    //building the AJAX return value for JSFiddle dummy AJAX endpoint
    var ret = {
        totalPage: 10,
        currentPage: page,
        items: []
    };
    for (var i = page; i < (page + 5); i++){
        ret.items.push(i);
    }

    //calling the AJAX
    $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        },
        success: function(data){
            if (data.currentPage <= data.totalPage){
                var filtered = data.items.filter(function(el){
                    return el % 2 == 1;
                });
                var newitems = items.concat(filtered);
                console.dir(newitems);
                request(data.currentPage + 1, newitems, defer);
            } else {
                console.dir(items);
                //resolve the deferred
                defer.resolve(items);
            }
        }
    });
}

function requestAll(){
    var deferred = jQuery.Deferred();
    request(1, [], deferred);
    return deferred.promise();
}

requestAll().done(function(items) {
    // all ajax calls are done
});

OK, after much new promise learning, here's a fully promise version that makes use of promise chaining (returning a promise from a .then() handler). Concepts borrowed and learned from Benji's implementation, but this is organized a bit differently and commented for learning (it would actually be quite short without comments and without the dummy Ajax call stuff):

function requestPages(startPage, endPage) {

    function request(page, items){    
        // building the AJAX return value for 
        // JSFiddle dummy AJAX endpoint
        var ret = {
            currentPage: page,
            items: []
        };
        for (var i = page; i < (page + 5); i++){
            ret.items.push(i);
        }

        // Do Ajax call, return its promise
        return $.ajax({
            url: '/echo/json/',
            method: 'POST',
            dataType: 'json',
            data: {
                delay: 1,
                json: JSON.stringify(ret)
            }
        }).then(function(data) {
            // mock filter here to give us just odd values
            var filtered = data.items.filter(function(el){
                return el % 2 == 1;
            });
            // add these items to the ones we have so far
            items = items.concat(filtered);

            // if we have more pages to go, then do the next one
            if (page < endPage){
                // Advance the currentPage, call function to process it and
                // return a new promise that will be chained back to the 
                // promise that was originally returned by requestPages()
                return request(page + 1, items);
            } else {
                // Finish our iteration and 
                // return the accumulated items.
                // This will propagate back through 
                // all the other promises to the original promise
                // that requestPages() returned
                return(items);
            }
        });
    }    

    // call the first request and return it's promise    
    return request(startPage, []);
}

// request pages 1 through 10 inclusive
requestPages(1, 10).done(function(items) {
    // all ajax calls are done
    console.log(items);
});

Working jsFiddle: http://jsfiddle.net/jfriend00/pr5z9/ (be patient, it takes 10 seconds to execute for 10 Ajax calls that each take 1 second).

One issue I noticed about this version is that because it only uses the promises created by $.ajax(), the code cannot do a .notify() to trigger progress notifications. I found that I wanted to trigger a progress notification on the originally returned promise as each Ajax call completed, but without creating my own Deferred, I couldn't do that because you can't do a .notify() on a promise, only on a Deferred. I'm not sure how to solve that and keep with Benji's architecture of not creating/resolving your own deferred.

like image 20
jfriend00 Avatar answered Sep 29 '22 23:09

jfriend00