0

How should I write my recursive loop to correctly execute promises in order? I've tried with Promise.all(Array.map(function(){})); which does not suit my needs as those steps need to be ran in a sequence. I've tried with a custom promise for I've found here, but it also has problems.

The promise for:

var promiseFor = (function(condition, action, value) {
    var promise = new Promise(function(resolve, reject) {
        if(!condition(value)) {
            return;
        }
        return action(value).then(promiseFor.bind(null, condition, action));
    });
    return promise;
});

The problem with this for is that it seems to stop at the deepest recursive call, not returning to continue executing the loop to finish correctly.

For example: in PHP a code like this:

function loopThrough($source) {
    foreach($source as $value) {
        if($value == "single") {
            //do action
        } else if($value == "array") {
            loopThrough($value);
        }
    }
}

If i pass lets say a folder structure and "single" means file, it will print all files. But the promise for I'm using stops at the first dead end.

EDIT: Added bluebird to see if it could help with anything, still the same thing. Here's the current loop code

var runSequence = (function(sequence, params) { 
    return Promise.each(sequence, function(action) {
        console.log(action['Rusiavimas'] + ' - ' + action['Veiksmas']);
        if(params.ButtonIndex && params.ButtonIndex != action['ButtonIndex']) {
            return Promise.resolve();
        }

        if(action['Veiksmas'].charAt(0) == '@') {
            var act = action['Veiksmas'];
            var actName = act.substr(0, act.indexOf(':')).trim();
            var actArg = act.substr(act.indexOf(':')+1).trim();

            /* This one is the code that figures out what to do and
               also calls this function to execute a sub-sequence. */
            return executeAction(actName, actArg, params);
        } else {
            sendRequest('runQuery', action['Veiksmas']);
        }
    });
});

I have 3 sequences, each consisting of 5 actions. First and second sequence has the next sequence as it's third action. Here's the result I get (numbers mean which sequence):

1 - @PirmasVeiksmas
1 - @AntrasVeiksmas
1 - @Veiksmas: list_two
2 - @PirmasVeiksmas
2 - @AntrasVeiksmas
2 - @Veiksmas: list_three
3 - @PirmasVeiksmas
3 - @AntrasVeiksmas
3 - @TreciasVeiksmas
3 - @KetvirtasVeiksmas
3 - @PenktasVeiksmas

As you can see it enters the next sequence and continues like it should, but once the third one is complete, it should resume the second sequence and finish up with the first one. But it stops as soon as it hits the first dead end in recursion.

EDIT2: Codepen example of what I have now and with visual representation of what's happening: Codepen link

Output should be:

fa1
second
sa1
third
ta1
ta2
ta3
sa3
fa3
Community
  • 1
  • 1
IntoDEV
  • 31
  • 8
  • is that php code asynchronous? – dandavis Apr 01 '16 at 10:03
  • @dandavis no, it was just an example, I just can't figure out why is the chain of promises stop when it hits the first dead end and must go back a step(recursion) to continue the previous for. – IntoDEV Apr 01 '16 at 10:43
  • Please always cite where you found code - attribute the author, and put a link there! Btw, [here's the original](http://stackoverflow.com/a/24660323/1048572) (which also does not have the mistake) – Bergi Apr 01 '16 at 12:17
  • @Bergi for a single loop that is, but when it comes down to recursive chaining it does not work. From what I've figured out it seems that the first deepest end when reached 'collapses' back to the beginning finishing up the promise chain, therefore stopping any previous looping. – IntoDEV Apr 01 '16 at 12:26
  • 1
    @IntoDEV: The problem is that it *doesn't* collapse back, the promises that your function returns are never resolved. Using the `Promise` constructor is totally wrong here. That's why I wanted you to link the source of that code, so that I can bash its author. – Bergi Apr 01 '16 at 13:09
  • @Bergi heh, do you know any alternatives or how should I go about it? Because the top priority here is step-by-step execution and waiting, the actions have ajax requests, user dialog boxes and must wait before those are done to continue next steps. – IntoDEV Apr 01 '16 at 13:19
  • @IntoDEV: Just fix the `promiseFor` function. It works when written correctly. – Bergi Apr 01 '16 at 14:34

2 Answers2

1

Running a bunch of actions in a sequence can be done with .reduce() instead of .map().

Here's an example:

// Helper function that creates a Promise that resolves after a second
const delay = (value) => new Promise(resolve => setTimeout(() => resolve(value), 1000));

const arr = [1, 2, 3, 4, 5];

// concurrent resolution with `.map()` and `Promise.all()`
Promise.all(arr.map(delay))
  .then(console.log.bind(console)); // [1, 2, 3, 4, 5] after a second.

// sequential resolution with `.reduce()`

arr.reduce((promise, current) => 
             promise
               .then(() => delay(current))
               .then(console.log.bind(console)), 
           Promise.resolve());
// second wait, 1, second wait, 2...

If I understood your requirements correctly, you don't exactly need Promise recursion, it's just the way you found to run Promises sequentially. .reduce() can help you with that more simply.

The reduction process turns [1,2,3,4,5] into:

Promise.resolve()
  .then(() => delay(1))
  .then(console.log.bind(console))
  .then(() => delay(2))
  .then(console.log.bind(console))
  .then(() => delay(3))
  .then(console.log.bind(console))
  .then(() => delay(4))
  .then(console.log.bind(console))
  .then(() => delay(5))
  .then(console.log.bind(console))

Take note that if you want to access all of the results, you need to do a bit more work. But I'll leave that as an exercise for the reader :)

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • Thanks, but I've already solved this problem with another stackoverflow question, turns out I was having problems with jQuery 2.x, since I moved to 3.x the problem solved itself. I wasn't using jQuery in the promises themselves to keep them independant, but just by having jQuery in my code messed up a couple of things. Edit: If you're interested here's the post [link](http://stackoverflow.com/questions/36426786/why-does-this-ajax-request-with-promises-break-the-code-as-running-it-without) – IntoDEV Oct 26 '16 at 11:01
0

So this fixes the problems with your codepen code so it gives the output you want.

var sequences = {
  first: ['fa1', 'second', 'fa3'],
  second: ['sa1', 'third', 'sa3'],
  third: ['ta1', 'ta2', 'ta3']
};

var loopThrough = (function(sequence) {
  return sequence.forEach(function(action) {
    return doAction(action);
  });
});

var doAction = (function(action) {
  var promise = new Promise(function(resolve, reject) {
    console.log(action);
    if(action == 'second' || action == 'third') {
      //recurse into sub-sequence
      return loopThrough(sequences[action]);
    } else {
      //do something here
    }
    resolve();
  });
  return promise;
});

loopThrough(sequences.first);
Arthur Cinader
  • 1,547
  • 1
  • 12
  • 22