JS Array Methods Guide: Array.some()

JS Array Methods Guide: Array.some()

A method you might not have heard about!

11 min read

If you have been coding JavaScript for a while, you must have written a code to check if an element in an array passes certain conditions. I did face such a situation, and I have written a function to achieve this. Although, I have recently found out that JavaScript already has a method for this called some()!

In this post, we will be looking into this method in a different approach than other blog posts. If you search on google, you can find tons of blog posts that explain how to use this method. I would like to take a bit of a different approach than this. I would like to talk deeper about this method and explain all the ins and outs of this method! So, buckle up! We are diving deep now 馃た!

The Question: Check if one of the elements passes the certain condition

In this post, we are trying to create a solution to check if one of the elements in an array, passes a certain condition. Let鈥檚 try to write some code! (If you are like me and you already know some method, let鈥檚 just pretend that we don鈥檛 馃か !). Our initial idea would be to loop through an array to check if one of the elements passes the condition. The code will look something like this:

function checkIfArrayHasEvenNumber(arr) {
  let result = false;
  for (let index = 0; index < arr.length; index++) {
    const element = arr[index];
    if (element % 2 === 0) {
      result = true;
    }
  }
  return result;
}

This is a very basic code that does the job! I would argue that it will fulfill your need in most cases. Although, it has its concerns. Let鈥檚 look at them one by one:

Concern 1: It does too many iterations

In the current state, this code will loop through the whole array before returning the result. So if the first item in your code passes the condition, it will still loop through the rest of the array. It does not seem too long right now. Although, if you are performing an expensive condition check, or if you are working with a large array, it will slow down your code a lot. We will look more into the performances concerns in the next step.

We can solve this problem by tweaking a few lines in this code. We need to return from the loop, as soon as we have the first element which passes the condition.

function checkIfArrayHasEvenNumber(arr) {
  for (let index = 0; index < arr.length; index++) {
    const element = arr[index];
    if (element % 2 === 0) {
      return true;
    }
  }
  return false;
}

Note: you can add the console.log statement before the return statements to check that the code is ending the loop right after it finds the first even number.

Concern 2: Validation

This change solved the performance issue, but there is a catch. The current code can throw unexpected errors in your code due to a lack of validation steps. If you look at the code, you will notice that we are assuming that the arr argument is an Array. As JavaScript does not provide type safety, it will throw an error when you pass a non-iterable object as an argument. We can add a few more lines of code to fix the issue:

function smartCheckIfArrayHasEvenNumberWithValidation(arr) {
  if (!(arr instanceof Array)) {
    throw new Error("Argument is not an array");
  }
  for (let index = 0; index < arr.length; index++) {
    const element = arr[index];
    if (element % 2 === 0) {
      return true;
    }
  }
  return false;
}

The first thing this function does is to confirm that the argument is an instance of an Array object. If you pass a non-array object to the function, you will receive an error like this in your console:

Screen Shot 2022-03-26 at 11.18.33 AM.png

The Solution: Array.some() method

We can already see that the function is growing as we solve these different edge cases. What if I tell you that you can perform the same logic as above with only 3 lines of code? Well, it鈥檚 not a surprise after reading the title of this blog post. Array.some() drop your need for implementing this feature from scratch! And it has its perks as well 馃槈! Let鈥檚 check the refactored code with the some method:

function checkIfArrayHasEvenNumberWithSomeMethod(arr) {
    return arr.some(function(el) {
        return el % 2 === 0;
    });
}

See! How clean and simple this code is! The some method expects a callback function that returns a boolean value. This callback function receives a current element of the iteration as a first argument. You can use this element to perform the condition check per your need and return a boolean value. The some method will use this return value to optimize your code. (For example, it will stop the loop automagically as soon as it receives the true return value for the first time!). In our example, we are returning the boolean result of our condition check (el % 2 === 0) for our callback function.

The some method will only exist on Array objects. JavaScript will by default throw an error if you try to call this method on an object other than an array. So, you get in-built validation, yay! The error will look something like this:

Screen Shot 2022-03-26 at 11.38.59 AM.png

This solved the messiness of our code. Although, we still have a major reason to discuss on why you should use .some() method instead! Let鈥檚 take a look.

Performance Testing

At this point, you can argue that the some() function only abstracts the logic from our function. As it does the same thing, why should I spend time replacing the code with the some() method? Yes, the some() method does abstract the code we have written so far, but, it does it better! Let鈥檚 do a performance test on all the different functions we have written and do a comparison.

I have written the following code to generate a very long 馃獦array. We will have a custom condition that will evaluate to true when it finds the element in middle. In a real-world scenario, the element will pass the condition at a random position. Although, in this testing, I will stick to a static condition (when the code finds an element in the middle of the array). This way, we can stay consistent with results.

// generate function to create array with 1000000 elements
function generateArray() {
  var arr = [];
  for (var i = 0; i < 1000000; i++) {
    arr.push(i);
  }
  return arr;
}

We will be testing four functions we have seen so far:

  • originalCheck
  • smartOriginalCheck
  • smartOriginalCheckWithValidation
  • someCheck

I will be using console.time and console.timeEnd methods to find the execution time. You can learn more about these methods on MDN Site here and here. You can find the complete code for the testing file on my GitHub profile here.

The Results:

Screen Shot 2022-03-28 at 7.16.21 PM.png

Shocking, right!?? If you are still reading the blog, you might have thought that some function will perform the same or better than our smart function with validation. But, the results are the opposite! The some function is taking almost 3~4 times to execute the code! At this point, I was very confused because I thought my code is replicating what some method is doing.

The implication of the some method in ECMAScript

This thinking is wrong! There is a major flaw in our testing. We can not compare the some method with a very basic for loop. If you look at the ECMAScript Specification, you will see that this method does way more! The explanation on the ECMAScript website is hard to understand, but we can look at the polyfill provided by MDN Site.

I would like to thank M1CH3L1US (Michael) 路 GitHub from Ben Awad | YouTube鈥檚 Discord server to help me understand how this method works.

// Production steps of ECMA-262, Edition 5, 15.4.4.17
// Reference: http://es5.github.io/#x15.4.4.17
if (!Array.prototype.some) {
  Array.prototype.some = function(fun/*, thisArg*/) {
    'use strict';
    if (this == null) {
      throw new TypeError('Array.prototype.some called on null or undefined');
    }
    if (typeof fun !== 'function') {
      throw new TypeError();
    }
    var t = Object(this);
    var len = t.length >>> 0;
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var I = 0; I < len; i++) {
      if (I in t && fun.call(thisArg, t[I], I, t)) {
        return true;
      }
    }
    return false;
  };
}

Let鈥檚 understand this code step by step.

  1. At first, the polyfill checks to make sure that this method is not being called on the null object. Please note that it does not check for its type because it is defined under the Array.prototype property.
  2. Now it makes sure that the callback function has a function type.
  3. The function now creates a duplicate of the array using the Object constructor.
  4. The function finds a valid object length using a right shift operator >>>.
  5. The function now abstracts the second argument if it exists
  6. At this point, the function starts a for loop for the same number of iterations as the value of the len variable
    1. If the array has a value associated with the current iteration index, it calls the callback function with the value.
      1. If the callback function returns true, the some function ends the loop and returns true.
  7. If the some function completes the loop without disruption, it returns a false value.

Dissection of the code:

We can notice that most of the polyfill logic is like our custom code except for a couple of steps: Step 4 and Step 6.1. These steps might feel unnecessary, but they are quite important. These steps will allow the method to handle a bad array.

In Step 4, we can see that the code is using >>> 0 on the length of the array. This is a hacky way to make sure the value is a positive integer value. I will talk more about this in the next section.

In Step 6.1, we are now making sure that we have a value associated with a current index. If it does, only then it will perform the callback function check.

The use of >>> (Right Shift Operator)

But wait a minute, if I am calling some function on an Array, how can an arr.length value be invalid? 馃 Let鈥檚 look at an example:

Because of the nature of the JavaScript, unfortunately we are able to do this:

var dangerousArray = Object.create([]);
dangerousArray.length = Infinity;

console.log(dangerousArray instanceof Array) // true
console.log(dangerousArray.length) // Infinity

As Array is an Object, we can override these values during runtime. Imagine, If someone passes a malicious array like this to your function, what will happen? Your app will crash! This will put your code into an infinite loop which will crash your code at runtime! To avoid issues with non-numeric values, the code uses /Right Shift Operator/ to convert them to a valid integer. If you run Infinity >>> 0, it will return 0 value. Because of this one line, it is preventing critical bugs.

Let鈥檚 look at another example to understand the Step 6.1:

var dangerousArray = Object.create([]);
dangerousArray.length = -1;

console.log(dangerousArray instanceof Array) // true
console.log(dangerousArray.length) // -1

Here, we have another bad array where the length value is negative. Now when you try to run -1 >>> 0, you will notice that the JavaScript returns 4294967295 because of the byte shifts. The Salman A user on stack overflow has explained this behavior well in this answer:numbers - Strange javascript operator: expr >>> 0 - Stack Overflow.

Unfortunately, the code will iterate 4294967295 many times if a value like -1 is passed as length. This value is way higher than the original length of the array. So now when you will try to fetch an item from the array at an invalid index, your code will crash. To prevent this error, the polyfill is doing an in check before running the callback function.

So, should you use the some method?

Absolutely! There are two major advantages of using the Array.prototype.some method:

  • This method performs a few important checks to prevent critical bugs at runtime.
  • This method makes your code much cleaner and simpler to understand. As it is a part of the official Array API, you do not have to manage and test your custom code to make sure it works!

Conclusion

This post is a story of some method to understand its inner working and caveats of using this method. Due to these runtime checks, it will take longer to execute the code. Although it will make your code look cleaner while making it more secure!

If you liked this in-depth explanation blog, Make sure to give a like to this post. I will also be waiting for your feedback on this blog. Also, please make sure to comment on which method I should write about next!

If you want to get notified about my new blog content, please make sure to subscribe to my newsletter. Also, follow me on Twitter (twitter.com/harsh_p_patel) and get the latest updates (with some silly questions 馃お)

References

Did you find this article valuable?

Support Harsh Patel by becoming a sponsor. Any amount is appreciated!