Chaining Promises recursively

Zane Geiger picture Zane Geiger · Sep 6, 2012 · Viewed 10.4k times · Source

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.

Answer

Chris Tavares picture Chris Tavares · Sep 7, 2012

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.