2

I need to execute a function after the recursive asynchronous ajax calls. I have:

var tree = [1, 2 ,3, 4];

recursion(0);

function recursion(i) {
    $.ajax('http://example.com/' + tree[i])
        .done(function (data) {
        // data is array
        ++i;
        if (tree[i] !==undefined) {
            $.each(data, function () {
                recursion(i);
            });
        }
    });
}

And I want after all calls when they are done to do:

recursion(0).done(function () {alert('All calls are done!')});

I know, I should use $.Deferred of JQuery, but ajax call return promise too. I'm trying to use $.Deferred but I encountered a problem with loop in this place:

     $.each(data, function () {
         recursion(i);
     });

Please, help me.

BottieYOYO
  • 23
  • 4
  • Your `recursion()` doesn't even return a promise. – Bergi Aug 28 '14 at 14:22
  • Have a look at [this question](http://stackoverflow.com/q/21762982/1048572) though and [here](http://stackoverflow.com/q/5627284/1048572) about how to wait for multiple `recurse()` promises – Bergi Aug 28 '14 at 14:25
  • 2
    Why is your recursive call in a loop over `data` when it actually doesn't use the `data` for the recursive call??? Does it need to be recursive at all? You might want to post your actual code, please. – Bergi Aug 28 '14 at 14:27
  • This is simplified example, the data used in the actual code – BottieYOYO Aug 28 '14 at 14:35
  • 1
    Please [edit] your question and show us how it is used, as well as how you want the result to look like. – Bergi Aug 28 '14 at 15:26

4 Answers4

2

I'm trying to use $.Deferred

Good!

but I encountered a problem with loop in this place: $.each(data, recursion)

Each of the recursion(i) calls returns a promise, so when we have kicked them off we're left with a collection of promises. Now, we can use $.when to wait for all of them, and get back a promise for all of their results.

Now, we use then to chain this looped execution after the ajax call, so that we can return a promise for the eventual result of this recursion step.

function recursion(i, x) {
    return $.ajax('http://example.com/' + tree[i] + x).then(function (data) {
        if (++i < tree.length)
            // get an array of promises and compose all of them
            return $.when.apply($, $.map(data, function(d) {
                return recursion(i, d);
            }));
        else // at the innermost level
            return data; // get the actual results
    }); // and return a promise for that
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Your Answer is getting stuck in loop :(, freezing browser , when tried at `console` , this page ; no return results at jsfiddle http://jsfiddle.net/guest271314/x8a8brak/ ? If possible, could create a jsfiddle , fork above , to demonstrate expected results ? fwiw , why not simply compose and post _own_ Answer instead of reviewing other users' efforts _before_ posting own Answer ? Or , both ? If your methods / Answer is truly most effective , Answer would speak for itself , no ? There could possibly be more than a single way , or "pattern" to employ to achieve same results ? – guest271314 Aug 28 '14 at 17:05
  • 2
    I think you have an off by 1 error on your `if(++i...` When `++i` equals `tree.length` then `tree[i]` will be undefined in the calls to `recursion`. You also define a parameter `x` but never pass it. Is that meant to be whatever bit of information is pulled from `data` in the loop? – James Montagne Aug 28 '14 at 17:44
  • @guest271314 For what it's worth, I very much appreciated Bergi's comments on my answer and as a result have improved my use of promises for the future. I assume my slowness in addressing his comments led to his posting his own answer. I often do much the same myself. If an answer is similar to what I would have answered but misses a few key points, i will point them out and allow the answerer to update his answer rather than posting a very similar answer. If the answerer isn't around (or willing) to update, I will post my own. – James Montagne Aug 28 '14 at 17:51
  • @JamesMontagne Yes , Bergi's comments _are_ appreciated , engaging. However , the requirement _could_ be accomplished _without_ utilizing `jquery.deferred/promise` ; see current post. See original post at "edited" , accomplishes task _with_ `jquery.deferred/promises` . Author _should_ still be free to explore and develop their own patterns ? Not certain exactly what seeking ? Solution , to achieve requirement described at OP ? , accrue awareness of possible "patterns" which _could_ achieve , ultimately, same result - with or without including `deferred` or ` promise` ? – guest271314 Aug 28 '14 at 18:25
  • 1
    @guest271314 Sorry, I hadn't seen the comment thread on your answer and was commenting based on the interaction on my answer alone. I don't want to read that thread in detail, but just in general, there is certainly value in different approaches to a problem, but also value in discussing the drawbacks of different approaches. That will help the original questioner as well as future visitors select the most appropriate solution for them. – James Montagne Aug 28 '14 at 18:51
  • @JamesMontagne Excellent Comment ! Concur ; rather healthy exchange at this Question , Answers , comments ; a modest variety of potential approaches to explore ! – guest271314 Aug 28 '14 at 19:09
  • @JamesMontagne: Thanks for your vigilance! Was in a hurry yesterday, have fixed both points now. – Bergi Aug 29 '14 at 10:33
  • @guest271314: I didn't post an answer on my own because I feel like I'm repeating myself (there must be a few very similar questions about recursive tree traversal with promises, I only didn't want to close everything as a duplicate). Also, James got it almost "right", so I wanted to let him write a good answer and pointed out some improvements (now we have essentially the same code). Sorry for bashing every answer that didn't use deferreds, but I'm a promise evangelist :-) I didn't want to downvote it (e.g. because of missing error handling), so I got into the discussion about improvements... – Bergi Aug 29 '14 at 10:49
  • @Bergi Not evangelist :), though probably consider `deferred`,`promise`, `callbacks`,`queue` most versatile parts of jquery. The process described at OP could be accomplished utilizing either of those implementations - or none - as tried to demonstrate at post. Ironically had composed a piece previous day; `deferred`, including `while` and `with`; that "waits" until all processes completed before doing other stuff. `notify` was in there, too! Probably could have mustered a "downvote" out of one of the High Priest's of the Promise :) if had posted that one! "Why `with` ??" Next time ;) Cheers ! – guest271314 Aug 29 '14 at 15:20
1

You would need to do something like this:

function recursion(i) {

    return $.ajax('http://example.com/' + tree[i])
        .then(function (data) {
            // data is array
            ++i;
            if (tree[i] !==undefined) {

                // get an array of promises
                var promises = $.map(data, function () {
                    return recursion(i);
                });

                // return the `when` promise from the `then` handler 
                // so the outer promise is resolved when the `when` promise is
                return $.when.apply($, promises);
            } else {
                // no subsequent calls, resolve the deferred
            }
        });
}

Currently untested, but it at least gives you the idea. Basically you only resolve the deferred once all of the subsequent calls are resolved.

James Montagne
  • 77,516
  • 14
  • 110
  • 130
  • Don't use the [deferred antipattern](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#wiki-the-deferred-anti-pattern)! You even know of [`$.when`](http://api.jquery.com/jQuery.when/)! – Bergi Aug 28 '14 at 15:43
  • @Bergi I feel like i'm probably missing something stupid in this case, but what are you calling `when` on? The list of deferreds doesn't exist at the time the function returns and the ajax call resolving isn't sufficient as that's too soon. – James Montagne Aug 28 '14 at 15:49
  • But the ajax promise does exist, on which you just need to [chain](http://api.jquery.com/deferred.then/) your recursive calls that are bundled by then `$.when`. Probably my comment was misleading, your use of `$.when` is correct, the use of `def` and `ajax().done(…)` is not. – Bergi Aug 28 '14 at 16:01
  • @Bergi Okay, now I've got your meaning. I haven't used `then` in that way. So you're saying use `then` instead of done and within the `then` function return the new promise. Very good point. Updating answer. – James Montagne Aug 28 '14 at 17:28
  • @JamesMontagne, thank you your answer also suitable. – BottieYOYO Sep 01 '14 at 07:08
0

To make it simple, you could just run a function in your .done that checks the value of i and if it's equal to the length of tree (minus 1), then run your function.

Sorry, that doesn't cover the asynchronous nature...instead...create a variable that tracks the number of completed and compare to the number in the array. When equal, run your function.

HTMLGuy
  • 245
  • 2
  • 17
0

Edit , jquery deferred , promise not actually needed to achieve requirement . See comment and link by Bergi , below.

Try (this pattern ; without jquery.deferred or promise's)

   (function recursion() {
        var tree = [1, 2 ,3, 4]
        , results = []
        , dfd = function(res) {
            alert('All calls are done!');
            console.log(res)
        };

    $.each(tree, function(k, v) {
        $.ajax("http://example.com/" + v)
        .done(function (data, status, jqxhr) {
        // data is array
          results.push([data, status, jqxhr.state()]);
            if (results.length === tree.length) {
              dfd(results);          
            }
        });

    });

    }());

jsfiddle http://jsfiddle.net/guest271314/nvs0jb6m/

guest271314
  • 1
  • 15
  • 104
  • 177
  • Why do you return the string `"pending"` from the IIFE? – Bergi Aug 28 '14 at 15:41
  • Don't use the [deferred antipattern](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#wiki-the-deferred-anti-pattern)! There already is [a function to do this](http://api.jquery.com/jQuery.when/). – Bergi Aug 28 '14 at 15:41
  • `pending` is `deferred.state()` prior to completing all tasks. Would be best to leave out deferred entirely from this one ? Will re-compose and post _without_ deferred . Thanks – guest271314 Aug 28 '14 at 15:45
  • @Bergi Piece updated. `deferred` , `promise` removed. Thanks, again. – guest271314 Aug 28 '14 at 15:51
  • No, no, you *should* use promises, but you should *return* one with the result of the recursive task. – Bergi Aug 28 '14 at 15:58
  • @Bergi Really ? Would it not be an "antipattern" to utilize promises when _not actually needed to achieve requirement_ ? Initial post actually _did_ return a `promise` object ! i.e., `dfd.promise().done()` - or "verbose" version of `$.when()` . fwiw , if comment suggest altering a working pattern , perhaps more illuminating to provide detailed description of shortcomings of a pattern - with an accompanying link to an article of a topic. Link does provide some insight into promise/def. patterns , may still be within the scope of the authors' discretion to create their own patterns. Thanks – guest271314 Aug 28 '14 at 16:20
  • No, it's an antipattern *not* to use promises, as they would simplify the code a lot, instead of your error-prone callback thingy :-) None of your previous versions did actually return a promise, no. For the links have a look at my initial comment on the question. – Bergi Aug 28 '14 at 16:20
  • @Bergi See at edits `dfd.promise().done(function (res)` . Is this not `promise` ? While composed with `return `dfd.state()` , the actual result of `.promise().done()` does not occur until _after_ `return dfd.state()`. btw, would have included `dfd.notify` for that purpose, as it was only to notify user of `progress` of tasks - yet , well, will leave `notify/progress` out of this one ;) . fwiw, the `promise` interface adds no value to achieving the requirement . Only posted with promises at initial post due to Question including the topic. Can "chain" from within an ordinary `dfd` fn , if need. – guest271314 Aug 28 '14 at 16:28
  • @Bergi "error-prone callback thingy :-)" ? Shine the light on it !! Can't have error-prone thingy's popping around to and fro on the SO :) – guest271314 Aug 28 '14 at 16:34
  • Just return the promise itself, not it's (current) state. You don't have `notify`/`progress` for the purpose of signaling the arrival of the result, you have `resolve`/`done`! – Bergi Aug 28 '14 at 16:37
  • @Bergi If this is the rub , Can we agree that the initial post - excluding returning the state before the actual done callback completed - That is, `return dfd.promise().done(function (res)` -- was _not_ an "antipattern" ? – guest271314 Aug 28 '14 at 16:41
  • It still had the deferred antipattern in it :-) Have a look at my answer for a clean implementation… – Bergi Aug 28 '14 at 16:50
  • @Bergi Your Answer is getting stuck in loop , freezing browser , when tried at console , this page :( – guest271314 Aug 28 '14 at 17:00