loading...

can't use .filter() after .forEach()

sovietspy2 profile image Barney ・1 min read

Hi!

So I came across this problem.

I have an array of N elements. I'd like to run every element against a function.

so I would go:

myArray.forEach(item=>process(item)

Now I want to filter some of them and call another function on the rest of them.

   .forEach(item=>process(item)
   .filter( somelogic )
   .forEach( item=>postProcessSome(item) );

But that's not valid code. forEach returns undefined so I can't call filter. I can use map instead of forEach but that way I can't use one liners.

What's an elegant way to do this? For example in Java I could use .peek that acts like a foreach but returns the value.

Thanks for help.

Discussion

pic
Editor guide
Collapse
vonheikemen profile image
Heiker

.map is the only option here. If the problem is process return value you can do this.

myArr.map(item => (process(item), item))
  .filter( somelogic )
  .map(item => postProcessSome(item));

If process doesn't mutate item that should be fine. You could even make a helper function.

const tap = (fn) => (value) => (fn(value), value);

array.map(tap(item => process(item)))
// ... the rest
Collapse
lionelrowe profile image
lionel-rowe

Comma operator for implied return from arrow function is kinda gnarly... though I guess that's a matter of personal style preference.

Collapse
sovietspy2 profile image
Barney Author

TIL comma operator! Thanks!

Collapse
lionelrowe profile image
lionel-rowe

What's wrong with this? Easy to reason about and the same total number of lines, though line count is a bit of an artificial metric - better to prioritize readability.

myArr.forEach(item => {
  process(item)
  if (someLogic(item)) postProcess(item)
})
Collapse
sovietspy2 profile image
Barney Author

For readability this is definitely the winner and it only iterates over the array once.

Collapse
vishnumohanrk profile image
Vishnumohan R K

Use .map()

myArr.map(item => process(item))
  .filter( somelogic )
  .map(item => postProcessSome(item));
Collapse
lionelrowe profile image
lionel-rowe

This won't work unless process(item) returns item.

Collapse
mdor profile image
Marco Antonio Dominguez

You can even use a simple reduce

myArray
  . reduce((acc, item) => {
    // Filter if met some condition, and add the proccesed item
    if (somelogic)  acc.push(process.item)
   return acc;
  }, [])
 .forEach(item => /*anything */ )

/* Compact should be */
myArray
  . reduce(
    (acc, item) => (somelogic && acc.push(process(item)), acc), []
  )
  .forEach(item => /*anything */ )

Collapse
sovietspy2 profile image
Barney Author

Nice! Very cool utilization of reduce I like it.