0

I am calling a function which essentially returns a promise that resolves with either a list or nothing. I then call the same function for each item in the list. Eventually, all will resolve with nothing. I would like to run some code only when all resolve, but cannot figure out how to do that.

To simplify my problem I created this example. I have control only over the recursive function. Saving all the promises to an array and passing it to Promises.all() in sample below:

function randomLowerInt(int) {
  return parseInt(Math.random() * int);
}

function smallerArray(size) {
  const arr = [];
  const smallerSize = randomLowerInt(size);
  for (let i = 0; i < smallerSize; i++) {
    arr.push(i);
  }
  return arr;
}

function createPromise(arrLength) {
  const secnds = parseInt(Math.random() * 20) * 1000;
  const timeCreated = new Date().getTime();
  const p = new Promise(res => {
    setTimeout(() => {
      const timeResolved = new Date().getTime();
      res({
        timeCreated,
        timeResolved,
        arrLength
      });
    }, secnds);
  });
  return p;
}

function recursive(arr) {
  arr.forEach(() => {
    createPromise(arr.length).then(({
      timeCreated,
      timeResolved,
      arrLength
    }) => {
//      console.log({
//        timeCreated,
//        timeResolved
//      });
      const smallerArr = smallerArray(arrLength);
      recursive(smallerArr);
    });
  });
}
recursive([1, 2, 3, 4, 5]);

const promises = [];

function recursive2(arr) {
  arr.forEach(() => {
    const p = createPromise(arr.length).then(({
      timeCreated,
      timeResolved,
      arrLength
    }) => {
      const smallerArr = smallerArray(arrLength);
      recursive2(smallerArr);
      return ({
        timeCreated,
        timeResolved
      });
    });
    promises.push(p);
  });
}
recursive2([1, 2, 3, 4, 5]);
console.log('Waiting...');
Promise.all(promises).then(vals => console.log(vals));

doesn't work because Promise.all() will get called before the array is fully populated.

Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • What is the expected result of `recursive2(smallerArr)` within `.forEach()`? At which recursive call will `arr` not be an array? – guest271314 Feb 01 '19 at 01:36
  • Isn't the whole point of promises to embrace the async nature of JS? What I mean is that you can return Promises (which become values once resolved), which allows your program to evaluate unblocked while those promises are awaiting their return values. Console logging these values, which captures the state of the values at the point of the console.log, won't actually help you to know what the promises are resolving because they won't await any values the way runtime will await any underlying incomplete async operations. Helpful post: https://blog.domenic.me/youre-missing-the-point-of-promises/ – jaredgorski Feb 01 '19 at 01:47
  • What is the expected result of `vals` at `.then()` chained to `Promise.all()`? – guest271314 Feb 01 '19 at 01:50
  • @jaredgorski - all good points. However, there are use cases which drive multiple simultaneous async operations that all culminate in an information architecture the end user needs. One example might be the use of microservices or FAAS type systems. In this case, the OP has a good qeustion. – Randy Casburn Feb 01 '19 at 01:50
  • Which, by the way, drove the adoption of async/await in the JavaScript language. – Randy Casburn Feb 01 '19 at 01:51
  • @RandyCasburn Very interesting. In that case, anyone who can answer this has my rapt attention. Thanks for the question OP – jaredgorski Feb 01 '19 at 01:52
  • that's going to be @Bergi if paying attention. – Randy Casburn Feb 01 '19 at 01:53
  • @RandyCasburn Any user can answer the question provided what the expected result is. – guest271314 Feb 01 '19 at 01:54
  • @guest271314 It will be an array with no length, and the recursive forEach won't run. – Nachman Berkowitz Feb 01 '19 at 01:55
  • @NachmanBerkowitz `[[[]],[[[[[]],[[]]],[[[]],[]],[[],[]]],[[[]],[[[]],[[]]],[[[]],[[]]]],[[],[]],[[[],[[]]],[],[[]]]],[[[]],[]],[[[[]],[]],[],[[],[]],[[[]],[]]],[[[],[[]]],[],[[],[]]]]`? Are you trying to reduce the input array? Can you include the complete expected output at the question? – guest271314 Feb 01 '19 at 01:57
  • @jaredgorski the code was just to symplify my point. Like Randy Casburn commented there are other cases. Thanks. – Nachman Berkowitz Feb 01 '19 at 02:01
  • Presently it is not clear what the expected result is. – guest271314 Feb 01 '19 at 02:02
  • @guest271314 in my actual case each promise is adding fields to an object. I would like to than pass that object to another function. – Nachman Berkowitz Feb 01 '19 at 02:03
  • @NachmanBerkowitz Ok. How is that related to the code at the question? Specifically, what is the purpose of the recursive call to the same function at `recursive2(smallerArr)`? Are you trying to do something like [multiple, sequential fetch() Promise](https://stackoverflow.com/questions/38034574/multiple-sequential-fetch-promise)? What is the expected output of `vals` at `.then()` chained to `Promise.all()` at the code at the question? – guest271314 Feb 01 '19 at 02:04
  • @guest271314 - I do realize anyone can answer - that was just giving respect to a community member who is passionate on this topic. Didn't mean to mislead any new community members or upset those with mega-rep. – Randy Casburn Feb 01 '19 at 02:10
  • "......ohhhhhhhhhh" - my mind just now. In other words (@NachmanBerkowitz correct me if I'm wrong and I can shut up), you're hoping to set some sort of expectation for the array of promises passed to `Promise.all()`? And it's not accumulating all of the promises you end up passing before firing `.then()`? – jaredgorski Feb 01 '19 at 02:14
  • @RandyCasburn No worries. "respect" and "rep" are meaningless from perspective here. What is essential information is what the purpose of the recursive call to the same function and expected output is. – guest271314 Feb 01 '19 at 02:17
  • @guest271314 the expected output of the example would be a log of the creation and resolve times for all the promises, instead of just the first five. – Nachman Berkowitz Feb 01 '19 at 02:19
  • @NachmanBerkowitz Still not following what `vals` is expected to be and what the purpose of the recursive call to the same function is? Is the requirement to only log creation and resolve times? – guest271314 Feb 01 '19 at 02:21
  • @NachmanBerkowitz Is this the expected result `[{"timeCreated":1548988062256,"timeResolved":1548988062257},{"timeCreated":1548988062256,"timeResolved":1548988062258},{"timeCreated":1548988062256,"timeResolved":1548988062258},{"timeCreated":1548988062256,"timeResolved":1548988062259},{"timeCreated":1548988062256,"timeResolved":1548988062260},{"timeCreated":1548988062257,"timeResolved":1548988062260},{"timeCreated":1548988062258,"timeResolved":1548988062260},{"timeCreated":1548988062258,"timeResolved":1548988062261},...]` having `.length` `20`? – guest271314 Feb 01 '19 at 02:29
  • @guest271314 in this example yes, only log creation and resolve times. In real case to get object populated by all promises. – Nachman Berkowitz Feb 01 '19 at 02:32
  • @guest271314 yes. Length would vary depending on math.random – Nachman Berkowitz Feb 01 '19 at 02:33

1 Answers1

0

If the expected result is only the timeCreated and timeResolved properties in a single object you can declare an array as a variable, .push() the values to the array return Promise.all() from .map() instead of .forEach() and log the result at chained .then().

The pattern can be substituted for use of .reduce(), async/await with for..of loop or other patterns.

let res = []
function recursive(arr) {
  return arr.map((promise) => {
    return createPromise(arr.length).then(({ timeCreated, timeResolved, arrLength }) => {
      console.log({ timeCreated, timeResolved });
      res.push({ timeCreated, timeResolved });
      const smallerArr = smallerArray(arrLength);
      return Promise.all(recursive(smallerArr));
    });
  });
}

Promise.all(recursive([1, 2, 3, 4, 5])).then(() => console.log(res));
guest271314
  • 1
  • 15
  • 104
  • 177