Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining Promises recursively

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.

like image 268
Zane Geiger Avatar asked Sep 05 '12 23:09

Zane Geiger


People also ask

How is a promise used in a recursive function?

This is the function that will be called recursively. Set the starting record location for the query based on the ExclusiveStartKey. Call the DynamoDB scan function. The promise() method “promisifies” the scan function to return a Javascript Promise.

What type of method is used in promise chaining?

A Promise is executed by using the . then() method written after the declared promise. If we need to handle any error which is occurred then we use the . catch() method written after promise.

Can promises be nested?

Nested Promise: In a promise nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait. If promised is not return, it will execute parallelly.

Can we chain promises in JavaScript?

JavaScript Promise Chaining Promises are useful when you have to handle more than one asynchronous task, one after another. For that, we use promise chaining. You can perform an operation after a promise is resolved using methods then() , catch() and finally() .


3 Answers

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.

like image 126
Chris Tavares Avatar answered Sep 23 '22 06:09

Chris Tavares


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.

like image 37
TKoL Avatar answered Sep 22 '22 06:09

TKoL


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.

like image 42
Jeff Brand Avatar answered Sep 24 '22 06:09

Jeff Brand