简体   繁体   中英

Javascript promise recursion and chaining

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

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 :)

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM