1

I have a dynamic number of promises that I need to run sequentially. I understood how I can run sequentially promises but I don't succeed to make it dynamic with a number of promises that could vary.

Here is a way I found to do it statically How to resolve promises one after another? :

function waitFor(timeout) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   resolve(`Finished waiting ${timeout} milliseconds`);
  }, timeout);
 });
}

waitFor(1000).then(function(result) {
 $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(2000);
}).then(function(result) {
    $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(3000);
}).then(function(result) {
 $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div id="result"></div>

I would like to do the same but instead of 3 nested promises, I would like to have any number I want. Can you help me ?

Thanks a lot!!

Mike
  • 49
  • 10
  • I try to convert promises to observables https://stackoverflow.com/questions/39319279/convert-promise-to-observable and join theirs using forkJoin – Eliseo Apr 29 '18 at 19:52
  • Thanks, how would it help to use observables ? I am not really familiar with these – Mike Apr 30 '18 at 06:29

4 Answers4

1

Make a seprate function to handle the number of iterations

function waitFor(timeout) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(`Finished waiting ${timeout} milliseconds`);
        }, timeout);
    });
}
function resultHandler(result) {
    $('#result').append(result+' @ '+(new Date().getSeconds())+'<br>');
    return waitFor(2000);
}
function repeat(promise,num){
    if(num>0)
    repeat(promise.then(resultHandler),num-1);
}

repeat(waitFor(1000),2)
Naman Kheterpal
  • 1,810
  • 1
  • 9
  • 16
  • Thanks a lot, I think using a recursion is indeed a good choice but I don't really understand how to use your code. How am I supposed to call resultHandler ? – Mike Apr 30 '18 at 06:28
  • Great I understand it now, I am going to adapt it for my case. Thanks a lot I see now how to chain promise with a function! – Mike Apr 30 '18 at 09:15
1

There are three basic ways to achieve this task with Promises.

  1. .reduce() pattern.

function waitFor(timeout) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   resolve(`Finished waiting ${timeout} milliseconds`);
  }, timeout);
 });
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    sequence = tos => tos.reduce((p,c) => p.then(rp => waitFor(c))
                                           .then(rc => console.log(`${rc} @ ${new Date().getSeconds()}`)), Promise.resolve());

sequence(timeouts);
  1. The recursive pattern.

function waitFor(timeout) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   resolve(`Finished waiting ${timeout} milliseconds`);
  }, timeout);
 });
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    sequence = ([to,...tos]) => to !== void 0 && waitFor(to).then(v => (console.log(`${v} @ ${new Date().getSeconds()}`), sequence(tos)));

sequence(timeouts);
  1. Scan from left pattern.

The scanl pattern would sequence promises one after another but once it is completed you also have access to the interim promise resolutions. This might be useful in some cases. If you are going to construct an asynchronous tree structure lazily (branching from the nodes only when needed) you need to have access to the previous promise resolutions.

In order to achieve scanl functionality in JS, first we have to implement it.

var scanl = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc))

we feed scanl with xs which is the array of timeouts in this particular example, f which is a callback function that takes acc (the accumulator) and e (current item) and returns the new accumulator. Accumulator values (the interim promise resolutions) are mapped over the timeouts array to be accessed when needed.

function waitFor(timeout) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   resolve(`finished waiting ${timeout} milliseconds`);
  }, timeout);
 });
}

var timeouts = [1000, 2000, 2000, 3000, 1000],
    scanl    = (xs, f, acc) => xs.map((a => e => a = f(a,e))(acc)),
    proms    = scanl(timeouts,                                             // input array
                     (a,t,r) => a.then(v => (r = v, waitFor(t)))           // callback function
                                 .then(v => (console.log(`${r} and ${v}`),
                                             `${r} and ${v}`)),
                     Promise.resolve(`Started with 0`));                   // accumulator initial value

// Accessing the previous sub sequential resolutions
Promise.all(proms)
       .then(vs => vs.forEach(v => console.log(v)));
.as-console-wrapper {
max-height: 100% !important
}
Redu
  • 25,060
  • 6
  • 56
  • 76
0

Forget I commented (when you convert a Promise to Observable or include the promise in an array, the Promise is executed). You can use a "recursive" function

  foolPromise(index: number, interval: number) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ id: index, data: new Date().getTime() % 100000 });
      }, interval);
    })
  }
  intervals: number[] = [1000, 50, 500];
  recursive(req: any, index: number) {
    req.then(res => {
      console.log(res);
      index++;
      if (index < this.intervals.length)
        this.recursive(this.foolPromise(index, this.intervals[index]), index);
    })
  }

  ngOnInit() {
    this.recursive(this.foolPromise(0, this.intervals[0]), 0)
  }
Eliseo
  • 50,109
  • 4
  • 29
  • 67
-1

If you don't care for serialization, you can use Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Promise.all([promise1, promise2, promise3]).then(function(values) {
  // do something with values
}).catch(function(err) {
  // error called on first failed promise
});

Or, you can use an async function:

async function doSomething(arrayOfPromises) {
  for (const item of arrayOfPromises) {
    const result = await item;
    // now do something with result
  }
};
Abid Hasan
  • 648
  • 4
  • 10
  • Thanks a lot but Promise.all will fire all promises at the same time and only the output will be in order. I want to fire promises one after another. – Mike Apr 30 '18 at 06:24