Beware of this trap: Why does every() in JS always return true for empty arrays?

The core of the JavaScript language is large enough that it’s easy to misunderstand how some parts of it work. Recently, when I was refactoring some every()code that used the method, I found that I actually didn’t understand the logic behind it. In my understanding, I thought that the callback function must be called and return before it returns true, but this is not actually the case. For an empty array, whatever the callback function is will be returned because that callback function has never been called. Consider the following situation:every()trueevery()true

function isNumber (value) {
    return typeof value === "number" ;
}

[1] .every (isNumber) ;             //  true 
[ "1" ] .every (isNumber) ;           //  false 
[1, 2, 3] .every (isNumber) ;       //  true 
[1, "2" , 3 ] .every (isNumber) ;     //  false 
[] .every (isNumber) ;              //  true

In each case of this example, the every()call to checks whether each item in the array is a number. The first four calls are fairly straightforward and every()produce the expected results. Now consider these examples:

[].every( () =>  true );            //  true 
[].every( () =>  false );           //  true

This may be even more surprising: callback functions that return trueor falsehave the same result. The only reason this can only happen is if the callback function is not called while every()the default value of is true. But why is an empty array returned trueto when there is no value to run the callback function every()with?

To understand why, we need to take a closer look at how the specification describes this method.

implement every()

ECMA-262 defines an Array.prototype.every() algorithm, which can be roughly translated into this JavaScript code:

Array . prototype . every = function ( callbackfn, thisArg ) {

    const O = this ;
     const len ​​= O. length ;

    if ( typeof callbackfn !== "function" ) {
         throw  new  TypeError ( "Callback isn't callable" );
    }

    let k = 0 ;

    while (k < len) {
         const  Pk = String (k);
         const kPresent = O. hasOwnProperty ( Pk );

        if (kPresent) {
             const kValue = O[ Pk ];
             const testResult = Boolean (callbackfn. call (thisArg, kValue, k, O));

            if (testResult === false ) {
                 return  false ;
            }
        }

        k = k + 1 ;
    }

    return  true ;
};

From the code, you can see every()that the result is assumed and is only returned if truethe callback function returns for any item in the array . If there are no items in the array, then there is no chance to execute the callback function, and therefore, the method cannot return .falsefalsefalse

Now the question is: why does every() behave like this?

“for all” quantifiers in mathematics and JavaScript

The MDN page provides the answer to why every()returns an empty array true:

every behaves like a “universal quantifier” in mathematics. Especially for empty arrays, it returns true. (It is an obvious truth that all elements in the empty set satisfy any given condition.)

Vacuous truth is a mathematical concept that means that something is true if a given condition (called antecedent) cannot be satisfied (that is, the given condition is not true). In JavaScript terms, every()an empty collection is returned truebecause there is no way to call the callback function. The callback function represents the condition to be tested and every()must return if it cannot be executed because there are no values ​​in the array true.

The “for all” quantifier is part of the larger topic in mathematics called “universal quantification” and allows you to reason about data sets. Given the importance of JavaScript arrays in performing mathematical calculations, especially when using typed arrays, it makes sense to have built-in support for such operations. And the every() method is not the only example.

“Existential quantifiers” in mathematics and JavaScript

JavaScript’s some()method implements the “existential quantifier” in existential quantification (“existence” is sometimes also called “existence” or “for something”). This “existential quantifier” stipulates that for any empty set, the result is false. Therefore, some()the method returns an empty collection falseand the callback function is not executed. Here are some relevant examples:

function isNumber (value) {
    return typeof value === "number" ;
}

[1] .some (isNumber) ;             //  true 
[ "1" ] .some (isNumber) ;           //  false 
[1, 2, 3] .some (isNumber) ;       //  true 
[1, "2" , 3 ] .some (isNumber) ;     //  true 
[] .some (isNumber) ;              //  false [ 
] .some (() => true );            //  false 
[] .some (() => false );           //  false

Quantification in other languages

JavaScript is not the only programming language that implements quantification methods for collections or iterable objects:

  • Python: all()Function implements “to all”, and any()function implements “existence”.
  • Rust: Iterator::all()Methods implement “for all” and any()functions implement “exists”.

The meaning and impact of the every() method of “universal quantifier” (for all)

Whether you think every()the method’s behavior is counterintuitive is open to debate. However, whatever your point of view, you need to understand every()the “for all” property of to avoid mistakes. In short, if you use the every() method or a potentially empty array, you should do an explicit check beforehand. For example, if you have an operation that relies on an array of numbers and fails if the array is empty, every()you should check to see if the array is empty before using .

function doSomethingWithNumbers(numbers) {

    // first check the length
    if (numbers.length === 0) {
        throw new TypeError( "Numbers array is empty; this method requires at least one number." );
    }

    // now check with every()
    if (numbers.every(isNumber)) {
        operationRequiringNonEmptyArray(numbers);
    }

}

Again, this only matters if you have an array that shouldn’t be used for operations when empty; otherwise, you can avoid this extra check.

in conclusion

I was surprised when I first saw every()the behavior on an empty array, but it makes sense once you understand the larger context of this operation and the widespread use of this feature in various languages of. If you are also confused by this behavior, then I suggest you change every()the way you read calls. Don’t every()understand it as “Does every item in this array meet this condition?” but should understand it as “Is there any item in this array that does not meet this condition?” This change in thinking can help you avoid future problems. An error occurred in the JavaScript code.