Combining forEach() with early returns

444 views Asked by At

My Problem

The ESLint airbnb policy disallows for...of loop iterations, and prefers forEach((element) => { code });. However, inner returns from that loop are swallowed - they are considered returns of the anonymous function rather than the function that embraces the loop.

Code

Original

Works, but breaks eslint-config-airbnb.

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;

function NestedIterator1() {
  const wordlist = [];
  for (const word of words) {
    if (word.length >= MIN_WORD_SIZE) {
      wordlist.push(word);
    }
    if (wordlist.length >= MAX_WORDS) {
      return wordlist;
    }
  }
  return wordlist;
}

console.log(NestedIterator1());

Alternative 1: Iterating array indices

Works, but the style is outdated and I have to manually assign values by indices.

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;


function NestedIterator2() {
  const wordlist = [];
  for (let i = 0; i < words.length; i += 1) {
    const word = words[i];
    if (word.length >= MIN_WORD_SIZE) {
      wordlist.push(word);
    }
    if (wordlist.length >= MAX_WORDS) {
      return wordlist;
    }
  }
  return wordlist;
}


console.log(NestedIterator2());

Alternative 2: Using forEach

Adheres to the style guide, but does not work - the inner returns are considered returns from the anonymous function, rather than the NestedIterator3.

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;

function NestedIterator3() {
  const wordlist = [];
  words.forEach((word) => {
    if (word.length >= MIN_WORD_SIZE) {
      wordlist.push(word);
    }
    if (wordlist.length >= MAX_WORDS) {
      return wordlist;
    }
  });
  return wordlist;
}

console.log(NestedIterator3());

My Question

How can a function iterate over an array while allowing early returns and avoiding indices and for..of iterations?

1

There are 1 answers

0
CertainPerformance On

One option would be to use reduce, which is very flexible and can be used in many situations where the other iteration methods aren't sufficient - only push to the accumulator if the accumulator's length is smaller than MAX_WORDS and of the word's length is sufficient:

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;

function NestedIterator3() {
  return words.reduce((wordlist, word) => {
    if (wordlist.length < MAX_WORDS && word.length >= MIN_WORD_SIZE) {
      wordlist.push(word);
    }
    return wordlist;
  }, []);
}

console.log(NestedIterator3());

Still, the above method does iterate over all indicies - it doesn't actually return early, it just doesn't do anything in the later iterations once the end condition has been fulfilled. If you want to actually break out of the iterator, you could use .some instead, although it's even more impure, and the intent of the code is slightly less clear IMO:

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;

function NestedIterator3() {
  const wordlist = [];
  words.some((word) => {
    if (word.length >= MIN_WORD_SIZE) {
      wordlist.push(word);
    }
    return wordlist.length === MAX_WORDS;
  }, []);
  return wordlist;
}

console.log(NestedIterator3());

For this particular example, you could also use filter followed by slice:

const words = ['harry', 'potter', 'and', 'the', 'forbidden', 'journey'];

const MIN_WORD_SIZE = 4;
const MAX_WORDS = 3;

function NestedIterator3() {
  return words
    .filter(word => word.length >= MIN_WORD_SIZE)
    .slice(0, MAX_WORDS)
}

console.log(NestedIterator3());

which certainly looks far more elegant, but the .filter necessarily iterates over all items in the array first, and so has the same problem as the reduce (no short-circuiting is going on) - in addition, those two chained methods only represent a subset of the situations in which one might want to short-circuit array iteration.