Chaining Promises recursively
Asked Answered
H

4

17

I'm working on a simple Windows 8 app in which I need to fetch a set of data from a web site. I am using WinJS.xhr() to retrieve this data, which returns a Promise. I then pass a callback into this Promise's .then() method, which supplies my callback with the returned value from the asynchronous call. The .then() method returns another Promise, giving it the value that my callback returns. The basic structure of such a query would be as follows:

WinJS.xhr({ url: "http://www.example.com/" }).then(
    function callback( result_from_xhr )
    {
        //do stuff
        return some_value;
    }).then(
    function secondcallback( some_value )
    {
        //do stuff
    });

In my situation, however, I may need to make additional queries for data depending on the data returned by the first query, and possibly more queries depending on THAT data... and so on, recursively.

I need a way to code this such that the final .then() is not executed until ALL the recursions have completed, similar to this:

function recurse() {
    return WinJS.xhr({ url: "http://www.example.com/" }).then(
        function callback( result_from_xhr )
        {
            if( result_from_xhr == something )
            {
               recurse();
            }
        });
}

recurse().then(
function final()
{
    //finishing code
});

The problem is that, of course, the finishing code is called as soon as the first level of recursion completes. I need some way to nest the new promise and the old promise from within the callback.

I hope my question is clear enough, I'm really not sure how to explain it and frankly the idea of asynchronous recursive code makes my head hurt.

Hoopoe answered 5/9, 2012 at 23:12 Comment(0)
C
14

What I would do here is to create a whole new, standalone promise that you can complete manually, and return that from the recurse() function. Then, when you hit the point that you know you're done doing async work, complete that promise.

Promise.join works when you've got a known set of promises - you need the entire array of promises available before you call join. If I followed the original question, you have a variable number of promises, with more possibly popping up as part of async work. Join isn't the right tool in these circumstances.

So, what does this look like? Something like this:

function doSomethingAsync() {
  return new WinJS.Promise(function (resolve, reject) {
    function recurse() {
      WinJS.xhr({ url: "http://www.example.com/" })
        .then(function onResult(result_from_xhr) {
          if (result_from_xhr === something) {
            recurse();
          } else {
            // Done with processing, trigger the final promise
            resolve(whateverValue);
          },
          function onError(err) {
            // Fail everything if one of the requests fails, may not be
            // the right thing depending on your requirements
            reject(err);
          });
    }
    // Kick off the async work
    recurse();
  });
}

doSomethingAsync().then(
function final()
{
    //finishing code
});

I rearranged where the recursion is happening so that we aren't recreating a new promise every time, thus the nested recurse() function instead of having it at the outer level.

Coriecorilla answered 6/9, 2012 at 22:50 Comment(1)
awesome, creation of the new promise is where I was stuck - your last statement was exactly what I neededEllita
J
5

I solved this problem in perhaps a different way (I think it's the same problem) while making my own Windows 8 app.

The reason I came across this problem is because, by default, a query to a table will paginate, and only return 50 results at a time, so I created a pattern to get the first 50, and then the next 50, etc, until a response comes back with less than 50 results, and then resolve the promise.

This code isn't going to be real code, just for illustration:

function getAllRows() {
    return new WinJS.Promise(function(resolve, reject){

        var rows = [];

        var recursivelyGetRows = function(skipRows) {
            table.skip(skipRows).read()
                .then(function(results){
                    rows = rows.concat(results);

                    if (results.length < 50) {
                        resolve(rows);
                    } else {
                        recursivelyGetRows(skipRows + 50);
                    }
                })
        }

        recursivelyGetRows(0);

    });
}

so getAllRows() returns a promise that resolves only after we get a result back with less than 50 results (which indicates it's the last page).

Depending on your use case, you'll probably want to throw an error handler in there too.

In case it's unclear, 'table' is a Mobile Services table.

Jadotville answered 4/7, 2015 at 16:54 Comment(2)
I suspect you need return statements to make this work, no?Farriery
No, it can be used pretty much as it appears. getAllRows() returns a promise which resolves with all the rows.Jadotville
M
1

You will need to use Promise.join().done() pattern. Pass an array of Promises to join(), which in your case would be a collection of xhr calls. The join() will only call done() when all of the xhr Promises have completed. You will get an array of results passed to the done(), which you can then iterate over and start again with a new Promise.join().done() call. The thing to be away of, when using this approach, is that if one of the Promises passed to join() fail, the entire operation is treated as an error condition.

Sorry I don't have time right now to try and stub out the code for you. If I get a chance, I will try to later. But you should be able to insert this into your recursive function and get things to work.

Magnificat answered 5/9, 2012 at 23:28 Comment(2)
I considered an approach using .join(), but found that (at least the way I was doing it) the Promises were, predictably, being added to the array after the join() call occurred (since they were being added after the asynchronous calls.) I'll look into using the results array and using the join within the recursive function as opposed to out. Thank you for the suggestion.Hoopoe
Check out Mike Tautly's blog - has some good stuff on Promises. Really helped me get more comfortable. mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/…Magnificat
H
0

Well, I solved my problem; my recursive function was misinterpreting the data and thus never stopped recursing. Thank you for your help, and I'll be sure to watch those screencasts, as I still don't quite fully grasp the Promise chaining structure.

Hoopoe answered 6/9, 2012 at 0:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.