The Magic of Chaining Methods
Until now, we have been using the map, filter, and reduce methods in isolation. However, we can take this one step further by chaining all these methods.
For example, let's consider again our movements
array.
const movements = [300, 600, -800, 7000, -750, -150, 90, 1500];
Now let's say that we wanted to take all the movement deposits, convert them from euro to dollars, and finally add them all up so that we know precisely how much was deposited into the account in US dollars. We could do each of these operations individually and store each result in a new variable. However, we can also do it all in one go.
Let's do that now.
const totalAmountDepositedUSD = movements
.filter((movement) => movement > 0)
.map((movement) => movement * 1.1)
.reduce((acc, movement) => acc + movement);
console.log(totalAmountDepositedUSD); // ==> 10439
We indeed get the expected result. But let me try and explain what's going on.
We want the sum of all deposits in US dollars. So the first thing to do is to take out all the deposits from the movements array. And by deposits, I mean all the positive movements. Hence, we need to filter the movements array to get only the positive movements.
movements.filter((movement) => movement > 0); // ==> [300, 600, 7000, 90, 1500]
Now that we've applied the filter method to the movements array, we know that the filter method returns a new array containing the elements that passed the test implemented by the provided function. So, we can now apply the map method on that new array to convert each movement from euro to dollars.
movements.filter((movement) => movement > 0).map((movement) => movement * 1.1);
// ==> [ 330, 660, 7700.000000000001, 99.00000000000001, 1650.0000000000002 ]
Like the filter method, the map method also returns a new array, with all the filtered movements converted to US dollars. We can, however, call for more filters and maps. But what we need next is to add all the movements together to get the total amount deposited. And for that, we can use the reduce method.
/* ‼️ You cannot chain an array method after reduce because it DOES NOT return an array. ‼️ */
movements
.filter((movement) => movement > 0)
.map((movement) => movement * 1.1)
.reduce((acc, movement) => acc + movement); // ==> 10439
You can imagine this as a pipeline that processes our data, where the input data is our movements array that goes through all these three steps (filter, map, reduce). And then, in the end, our input data comes out processed on the other side of the pipeline.
Next, when we chain all these methods together, it can be difficult to debug if one of the results is not what we expect. For example, if the final result returned by the reduce method was something really weird, say a negative number, we wouldn't know from which step of the pipeline it would come from. And to solve this, it would be good to check out the array in each step.
So let's say we made a mistake and wrote this:
movements
.filter((movement) => movement < 0) .map((movement) => movement * 1.1)
.reduce((acc, movement) => acc + movement); // ==> -1870.0000000000002
Our result would come out negative, and that would be strange. So, in this case, it will probably be nice to know the result of this filter operation. Hence, we can do that by using the array parameter we get access to in the map callback function.
const amountDeposited = movements
.filter((movement) => movement < 0)
.map((movement, index, arr) => {
console.log(arr); // ==> [ -800, -750, -150 ]
return movement * 1.1;
})
.reduce((acc, movement) => acc + movement);
This is a great use case for having access to the current array. Because as you can see, the array arr
has to be the result of the previous operation. That is, of the filter method. arr
is not the initial movements array because that's not what we called the map method on. The map method was called on the result of movements.filter
, and therefore that is the value of arr
.
To finish this lecture, let me give you a few remarks about chaining. First, we should not overuse chaining. We should try optimizing it because chaining tons of methods one after the other can cause real performance issues if we have huge arrays.
So if we have a huge chain of methods chained one after the other, we should try to compress all the functionality they do into as little methods as possible. For example, sometimes we create way more map methods than we actually need, where we could just do it all in just one map call.
So when you chain methods, keep looking for opportunities of keeping up your code's performance.
Secondly, it is a bad practice in JavaScript to chain methods that mutate the underlying original array. And an example of that is the splice method. So again, you should not chain a method like the splice or the reverse method. You can do that for a small application, but in a large-scale applications, it's usually always a good practice to avoid mutating arrays. And we will come back to this when we talk a little bit more about functional programming.
With that being said let's move on to the find
method.