ewack
ewack

Reputation: 195

How to use reduce in a Promise.all instead of map

How can I use reduce in the place of map when using Promise.all? My attempt results in an error UnhandledPromiseRejectionWarning: TypeError: #<Promise> is not iterable at Function.all (<anonymous>)

Eventually I would like to conditionally add innerResult to memo but I need to use reduce first.

const _ = require('lodash');

const eq = [{id:1}, {id:2}, {id:3}];

// block to replace
var biggerEq = _.map(eq, async (e) => {
  const innerResult = await wait(e.id);
  return innerResult;
})

// attempt at replacing above block
// var biggerEq = _.reduce(eq, async (memo, e) => {
//   const innerResult = await wait(e.id);
//   memo.push(innerResult)
//   return memo;
// }, []);

Promise.all(biggerEq).then((result) => {
  console.log(result) // outputs [ 2, 4, 6 ]
})


function wait (id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(id  * 2);
    }, 1000);
  })
}

Upvotes: 2

Views: 3289

Answers (2)

see sharper
see sharper

Reputation: 12035

I think CertainPerformance over-complicated it. You can use reduce like this with Promise.all:

const eq = [{id: 1}, {id: 2}, {id: 3}];

function wait(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(id  * 2);
    }, 1000);
  });
}

const biggerEq = _.reduce(eq, (arr, obj) => {
  const p = wait(obj.id);
  arr.push(p);
  return arr;
}, []);

Promise.all(biggerEq).then((arr) => {
  console.log(arr);
});

Note that the problem was with using await inside the reduce. That meant that you were pushing the results of the promises into the array, not getting an array of promises. Using that method, you already have your results in the array, so there's no need for Promise.all, but there's the big disadvantage that the promises are resolved consecutively. If that's actually what you want, you can have:

const results = _.reduce(eq, async (arr, obj) => {
  const p = await wait(obj.id);
  arr.push(p);
  return arr;
}, []);
console.log(results);

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370789

If you want to replace it with reduce, it's possible, but the logic will be a bit convoluted. Make the accumulator a Promise that resolves to an array that you can push to, then return it so the next iteration can use it (as a Promise):

const eq = [{id:1}, {id:2}, {id:3}];

function wait (id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(id  * 2);
    }, 1000);
  })
}

const biggerEq = _.reduce(eq, async (arrProm, obj) => {
  const [arr, innerResult] = await Promise.all([arrProm, wait(obj.id)]);
  arr.push(innerResult);
  return arr;
}, Promise.resolve([]));

biggerEq.then((arr) => {
  console.log(arr);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

(but .map is really more appropriate when you want to transform one array into another)

Upvotes: 3

Related Questions