Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript : Calling Recursive Functions With Promises

Language: JavaScript


Recursion - Not my Favorite Topic.

Promises - They can get confusing.

Recursion + Promises - I need to program in a padded room.

I made this little JS Fiddle puzzle I call The RecursiveFunHouse as comedic way of keeping my sanity by simplifying the issue into something silly. Hopefully yall can get a laugh out of my pain :)

The Problem:

Each recursive call is dependent on the outcome of the previous call, but in order to get the outcome I must run an asynchronous task and use that outcome in other sub tasks.

The "Recursive Fun House" helped me boil down the issue to this - the original recursive loop is continuing on with an undefined value as the sub tasks are still executing.

The Fun House - loops around collecting random numbers between (-99) and 99. Once the last difference between the last number and the new number is positive the fun is over

Printing "Made it here 1...Made it here 6" should indicate that the sub-tasks were handled correctly and we have a value for the next loop.

Current it prints 1,2,3,6,4,5 :(

recursiveFunHouse.js

var recursiveFunHouse = function(num){
    console.log("Made it here 1");
    var newNum = performSideTasks();

    console.log("Made it here 6");
    console.log("newNum");
    console.log(newNum);
    if(newNum-num >0 ){
            recursiveFunHouse(newNum);
    }
    else{
        console.log("The FunHouse Generated These Numbers :")
      for(var i = 0; i <numList.length; i++){
         console.log(numList[i]);
      }
    }

};

var performSideTasks = function(){
    console.log("Made it here 2");
    someAsyncTask().then(function(num){
            anotherTask(num);
      console.log("made it here 5");
      return num;
        });


}

var someAsyncTask = function(){
  return new Promise (function(resolve, reject) {
    console.log("made it here 3");
    var randNum = Math.floor(Math.random()*99) + 1; 
    randNum *= Math.floor(Math.random()*2) == 1 ? 1 : -1;   

    setTimeout(function() { 
      numList.push(randNum)
      resolve(randNum)
    }, 100);
  });
}




var anotherTask = function(num){
  console.log("made it here 4");
    console.log(num);

};

var numList= [];

recursiveFunHouse(20);

Note - Forgive my pathetic return statement return num; It just shows what I wish I could tell my computer.

Questions About Recursion and Promises:

1) Should I be worried in the first place about going into the next recursive loop with a promise not yet resolved?

2) If not, what is a clean way to keep this function decomposition and force each recursive loop to wait for the resolution of the last call?


Recursion and Promises can seem wizard-level-95 magical sometimes... that's all I'm saying. Question-Done.

like image 390
Nick Pineda Avatar asked Jan 10 '16 22:01

Nick Pineda


People also ask

How is a promise used in a recursive function?

When working with a recursive method that returns a promise, the most important thing to remember is that each time the method is called, they create a new promise. All of those promises need to be resolved in order for the original call to be resolved.

How do you design a recursive function in JavaScript explain with an example?

Recursion is a process of calling itself. A function that calls itself is called a recursive function. The syntax for recursive function is: function recurse() { // function code recurse(); // function code } recurse();

How do you call an anonymous recursive function?

Anonymous recursion primarily consists of calling "the current function", which results in direct recursion. Anonymous indirect recursion is possible, such as by calling "the caller (the previous function)", or, more rarely, by going further up the call stack, and this can be chained to produce mutual recursion.

Is recursion a callback function?

Call Back function - Its same sa that of recursive funcion. t may be an infinte loop function also. Recursion means calling itself again and again.


1 Answers

In classical synchronous recursion, recursion state is stored in stack frames pushed and popped off a common execution stack. In asynchronous recursion, recursion state can be stored in Promise objects pushed and popped off the head of a common promise chain. For example:

function asyncThing( asyncParam) { // example operation
  const promiseDelay = (data,msec) => new Promise(res => setTimeout(res,msec,data));
  return promiseDelay( asyncParam, 1000); //resolve with argument in 1 second.
}

function recFun( num) { // example "recursive" asynchronous function

    // do whatever synchronous stuff that recFun does when called
    //      ...
    // and decide what to do with async result: recurse or finish?

    function decide( asyncResult) {
        // process asyncResult here as needed:
        console.log("asyncResult: " + asyncResult); 
        if( asyncResult == 0)
            console.log("ignition");

        // check if further recursion is needed:
        if( asyncResult < 0)
            return "lift off"; // no, all done, return a non-promise result
        return recFun( num-1); // yes, call recFun again which returns a promise
    }

    // Return a promise resolved by doing something async and deciding what to do.
    // to be clear the returned promise is the one returned from the .then call

    return asyncThing(num).then(decide);
}

// call the recursive function
recFun( 5)
.then( function(result) {console.log("done, result = " + result); })
.catch( function(err) {console.log("oops:" + err);});

Run the code to see its effect.

Core principles (magic) on which this example relies:

  1. then registration of listener functions returns a pending promise. If a listener is called and returns from execution, listener return value resolves the pending promise. If the listener throws an error instead of returning, the pending promise is rejected with the thrown value.
  2. A promise cannot be fulfilled with a promise. If a listener returns a promise, it is inserted at the head of what remains of the promise chain, before the promise returned from listener registration. The promise from listener registration (previously the head of the residual promise chain) is then synchronized with the inserted promise, adopting its state and value when finally settled.
  3. All promise objects and linkages for a chain of promises chain are created synchronously when the code defining the chain executes. The definition code then runs to completion (meaning it returns to the event loop without interruption by asynchronous callbacks because JavaScript is single threaded).

If and when listener functions are executed (because a promise becomes fulfilled or rejected) they are executed asynchronously, in their own call out from the event loop, after code which resulted in the listener being executed has itself run to completion.

This means all log entries made when registering promise listeners (by calling then on a promise) appear before any log entry made by a registered listener function when executed asynchronously at a later time. Promises do not involve time travel.

Will this stop your head hurting? Perhaps not, but at least it's true.

`

like image 116
traktor Avatar answered Sep 17 '22 12:09

traktor