Clean up your conditionals with array methods
- javascript
I think we’ve all been there at some point in our coding paths. We have a simple if
statement with a condition—all nice and tidy. But it soon turned out that there was another condition that needed to be checked. We have two conditions now.
Easy peasy, just use &&
or ||
operators, depending on a situation. The if
statement will be something like if(condition1 && condition2)
. There’s nothing wrong here (actually, to my surprise, I wasn’t able to find a single eslint rule disallowing that).
But a few moments later, we find out that we don’t need just two, but three conditionals, and our code starts to look quite untidy, e.g.:
let ham = 10 let pineapple = 20 let tomatoSauce = 12 if (ham > 0 && pineapple > 0 && tomatoSauce > 0) { console.log("Hooray! Can make pineapple pizza") }
💡 There are quite elegant solutions to this.
As in the example above, the conditions are often fairly related. Even if it may appear that they are not related, grouping them into a single statement always results in a relationship. Thus, it shouldn’t matter to move things further and create a dedicated list, or array
, of the conditions, e.g.:
let assets = [ham, pineapple, tomatoSauce]
Now, we’ll be able to take advantage of the native array
methods to help us check the multiple conditions.
Array.every()
The first method, which might not get the appreciation it deserves, is array.every()
. It iterates over the array
list and invokes a callback function over each element, just like most other ES5 array methods.
The callback’s return value is coerced to boolean
(just like anything put into if
statement—see the pattern?).
Unlike most ES5 array methods (map
, filter
, reduce
etc.), it short circuits and returns false
when any of the callbacks returns a falsy value, which is a bonus point regarding performance.
We can utilize array.every()
like that:
let ham = 10 let pineapple = 20 let tomatoSauce = 12 let assets = [ham, pineapple, tomatoSauce] if (assets.every(asset => asset > 0)) { console.log("Hooray! Can make pineapple pizza") }
See? Way cleaner than the version with multiple &&
‘s.
Array.some()
Array.some()
can be seen as the inverted version of array.every()
. It also iterates over an array
, but short circuits and returns true
if any of the callbacks returns a truthy value.
The conditional check utilizing array.some()
evaluates to true
if any of the assets is less than 1:
if (assets.some(asset => asset < 1)) { console.log("So sad, can't make the best pizza") }
Array.includes()
Array.includes()
is another useful array method. It is a more recent one, added by ES7. It checks if the array includes something—in other words, if there’s a certain element present.
It behaves similarly to the older array.indexOf()
, but unlike it, it simply returns true
or false
depending on whether the element was found or not (whereas array.indexOf()
returns the element’s index or -1
if it was not, which can be confusing).
We can use array.includes()
like that:
if (assets.includes('pineapple')) { // not a fan of pineapple pizza console.log("Yuck! I won't eat that!") }
Using Set instead of Array
Regarding the array.includes()
method, there can be a slight drawback: the algorithmic complexity (the ’big O’ notation).
Worst-case scenario, the complexity of array.includes()
can be O(n): element is not found and our program is iterating over the whole array. It would be better if the element is found, because it short circuits.
It’s unlikely that we’ll have to worry about it much, but if we do need to be performant, the solution is straightforward. We can convert our array
into a Set
data structure.
The set
is a javascript implementation of a hash map (or hash table), and like it, it can be as performant as O(1). The equivalent of array.includes()
method is set.has()
:
// O(1) const setOfAssets = new Set(assets) if (setOfAssets.has('pineapple')) { //... }
Tip: check another javascript hashmap implementation, the Map
data structure.
Abstract the logic into a predicate function
It can be a good practice to extract (abstract) the logic of the examples above into a specialized function. There is an important reason for that: it forces us to think about what we are actually checking in order to name the function (and, as a bonus, it can make the code even more tidy).
Because the conditions check function will most likely return a boolean, it is best to give it a name that begins with is or has. There is actually a name for a function like that—a predicate.
So applied to the above examples, the resulting code can look like this:
const hasAssetsForPineapplePizza = assets => assets.every(asset => asset > 0) if (hasAssetsForPineapplePizza(assets)) { console.log("Hooray! Can make pineapple pizza") }
…which is quite clean and easy to reason about.
👍 Enjoy!
If you find anything in this post that should be improved (either factually or in language), feel free to edit it on Github .