How To Use .every() and .some() to Manipulate JavaScript Arrays

PostedDecember 12, 2019 211 views DevelopmentJavaScript

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Introduction

ES5 and ES6 brought many changes to JavaScript, including better ways of working with a collection of data expressed as arrays. While the language has significantly improved support for declarative data manipulation on arrays and many modern browsers support them, only a limited subset of array capabilities are commonly used. In particular, the .every() and .some() functions can improve how developers manipulate arrays, and potentially give them performance gains.

This article will show that the prominent JavaScript array functions are .map(), .filter(), and .reduce(), and will then go through examples of instances in which .every() and .some() would save computing power over the more prominent solutions.

.map(), .filter(), and .reduce(): The Prominent JavaScript Array Functions

Usage of .map(), .filter(), and .reduce() are very common. For the purpose of this article, let’s call these three functions MFR.

These functions have gained more prominence over the other functions for manipulating array data in JavaScript. Google Trends, for example, shows more active searches for MFR than a sampling of other functions:

Google trends comparison of `.foreach`, `.filter`, `.map`, `.reduce`, and `.some` search frequency. `.filter`, `.map`, and `.reduce` are the highest.

Google Search also shows that search queries for MFR produce far more results.

Bar graph comparing `.map`, `.filter`, `.reduce`, `.every`, and `.some` in terms of number of Google searches. `.map`, `.filter`, and `.reduce` are the highest by a large margin.

Search results for .filter() are as much as over 74M. This is 99.97% higher than results for .every(), and 99.98% higher than results for .some(). This means people are talking, writing, and teaching more MFR, which suggests more usage over time.

The Benefits of .every() and .some()

In JavaScript, it is generally taught to limit computations on arrays to iteration (.forEach) and transformation (.map, .filter, .reduce - a.k.a MFR) operations. This could lead to computations that could be done with .some() or .every() to be forced into MFR , especially a sequence of .filter() followed by a .reduce().

Almost every usage of MFR where you need to exit early and not comb through the entire collection is a prime candidate for .some() or .every(), because they both short-circuit and exit the array iteration as early as possible, instead of running till the end and potentially wasting compute resources.

In a time when we are building more complex apps and shipping more bytes to the client, resulting in significant overhead to parse JavaScript (see TTI, anything we can do to have three iterations (due to short-circuit) instead of thirty is highly welcome.

Array.prototype.every()

The every() method allows us to establish if every element within an array meets a certain requirement. It stops evaluating the array (short circuits) the first time it finds an element that does not satisfy the given requirement.

Array.prototype.some()

The some() method allows us to establish if some (at least one) elements within an array meets a certain requirement. It stops evaluating the array (short circuits) the first time it finds an element that does satisfy the given requirement.

One Example, Two Scenarios

Let’s say you have been asked to write a simple add() function to add a bunch of integers. The function should expect to be given any number of integers to be added and it should return their sum after computing the addition.

The following are two ways to complete this task.

Scenario One

If the add() function is given mixed input (e.g, integers, floats, undefined, strings, etc.), it should proceed with just the integers and return the sum from adding them.
We could implement our add() function this way:

const add = (...entries) => {
  return entries
    .filter(Number.isInteger)
    .reduce((sum, int) => {
      return sum + int;
    }, 0);
};

Since this scenario expects the add() function to compute the sum from whatever integers exists within the given inputs, one might implement the function like we have in the previous code block. We first use .filter() to extract only the integers and then compute the sum with a .reduce(). The .filter() operation will however iterate over the entire array even if there are no integers within it, wasting compute resources.

One can also argue that we can get a better solution like this:

const add = (...entries) => {
  return entries.reduce((sum, entry) => {
    if(Number.isInteger(entry)) {
      return sum + entry;
    }
    return sum;
  }, 0);
};

This take on add() is a little better because, unlike the first one that iterates to identify valid data and then further iterates to compute the result based on the valid data, now there is just one block of iteration. However, we are still running through to the end of the array even if there might be no integers within it.

We need a way to first tell if the gathered inputs have at least one integer. Since we are trying to find the sum from the inputs, we actually need at least two integers in there. We will proceed to reduce the inputs to the sum of the integers if we can find two or more integers.

Lets use .some() so ensure this condition is met:

const add = (...entries) => {
  let theSum = 0;
  if(hasTwoOrMoreInts(entries)){
    // there are >= 2 integers, lets sum them
    theSum = entries.reduce((sum, entry) => {
      if(Number.isInteger(entry)) {
        return sum + entry;
      }
      return sum;
    }, 0);
  }
  return theSum;
};

Now we have a condition that prevents the sum calculation unless we are sure there are two or more integers. We are doing this with a hasTwoOrMoreInts() function that we will be creating in the following:

const hasTwoOrMoreInts = (entries) => {
  let lastIndex = -1;
  let hasMinimumIntsCount = false;

  const hasAnInt = entries.some((entry, index) => {
    lastIndex = index;
    return Number.isInteger(entry);
  });

  if(hasAnInt === true) {
    // we've got one int, is there another?
    const hasMoreInts = entries.slice(lastIndex + 1).some(Number.isInteger);
    hasMinimumIntsCount = (hasMoreInts === true) && hasAnInt;
  }

  return hasMinimumIntsCount;
};

Scenario Two

If the add() function can receive mixed input (such as integers, floats, undefined, strings, etc.) but needs to only proceed with computing the sum of the given inputs if all the inputs are integers, a common approach influenced by the prominence of MFR might look like this:

const add = (...entries) => {
  let theSum = 0;
  const nonInts = entries.filter(entry => !Number.isInteger(entry));
  if(nonInts.length === 0) {  // are there non-ints?
    theSum = entries.reduce((sum, int) => {
      return sum + int;
    }, 0);
  }
  return theSum;
}

Again entries.filter() attempts to see if there are invalid inputs by iterating the entire entries array to gather every input that is not an integer. If there are no invalid inputs (nonInts.length === 0), we compute the sum with .reduce(). This would have made sense if we needed the total number of all invalid inputs or all the inputs themselves. Otherwise, we should be looking for the first invalid input and deciding what to do next from there.

Like .some(), .every() will act on the least information necessary. In this case, once it finds an input that is not an integer, it will look no further (by exiting with a return of false) and allow us to proceed with the next important thing.

Let’s see how .every() does this for us:

const add = (...entries) => {
  let theSum = 0;
  const areAllInts = entries.every(Number.isInteger);
  if(areAllInts === true) {  // are these indeed all ints?
    theSum = entries.reduce((sum, int) => {
      return sum + int;
    }, 0);
  }
  return theSum;
};

Since entries.every() will return false as soon as it finds what is not an integer, we are able to exit further tests for invalid elements within entries, freeing up resources that might be needed to give a mobile user a smooth scrolling experience.

A More Practical Example

Programmers don’t often write add() functions that often from day to day, so let’s look at a real-world example: applying .every() and .some() to a hypothetical HTML form validation.

We want to submit the form only after all required data have been obtained and validated from the form. We equally want at least one of the optional data to be filled in:

const requiredFields = Array.of(
  isFirstNameValid(),
  isLastNameValid(),
  isEmailValid(),
  isAboutMeValid()
);

const optionalFields = Array.of(
  isTwitterValueValid(),
  isFacebookValue(),
  isGoogleplusValueValue()
);

const isValid = (inputStatus) => inputStatus === true;

if(requiredFields.every(isValid) && optionalFields.some(isValid)) {
  // all required fields are valid
  // and at least one social media field is valid
  // lets proceed to submit the form now
} else {
    // lets tell the user we are serious here
    // this will happen really fast since we are short-circuiting 
    // with .some and .every above
}

As seen above, all the functions within Array.of() are doing the validation for specific form fields, and then returning either true or false. Since Array.of() is a factory for constructing arrays from the parameters it is given (the validation functions in our case), this means optionalFields can eventually look like [true, false, false]. This way, if any value in requiredFields is false, or if none of optionalFields is true, we do not submit the form. The form submission is decided with the shortest path of execution.

Conclusion

This exploration helped reveal how prominent MFR has become. It also sheds light on why .every() and .some() can give you more efficient strategies for manipulating arrays.

Here are some possible scenarios to promote or adopt .some() or .every() over MFR in your Javascript codebase:

  • A .filter() immediately followed by .forEach(), .map(), .reduce(), or any chained combination involving them.
  • A .filter() immediately followed by a condition examining the result of the invocation, and where the body of the condition contains a .forEach() , .map(), .reduce(), or any chained combination involving them, acting on the result from the .filter().

For these and other relevant scenarios, exiting the checks as soon as possible will optimize your resources.

0 Comments

Creative Commons License