Arrays methods (map, filter, forEach and reduce)

JavaScript, according to Wikipedia, can be considered as an example of functional programming, which has the following characteristics:

"When a pure function is called with some given arguments, it will always return the same result, and cannot be affected by any mutable state or other side effects.

Does all these arrays methods (map, filter, forEach and reduce) fit into this definition? Let's discuss using examples from JSONPlaceholder, a popular free fake API for testing.

Data

The data that we are going to manipulate are from this async function:

async function fetchText() { const response = await fetch('https://jsonplaceholder.typicode.com/users/'); const originalData = await response.json(); console.log('originalData', originalData) } fetchText()

The result is an array of objects:

// originalData [ { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }, ... ]


Array.map()

Array.map is a powerful function that enables us to transform one array into another, while the original array is left untouched and the function returns a new array.

Therefore, it can be seen as a pure function as it does not change the data structure of the original array.

async function fetchText() { const response = await fetch('https://jsonplaceholder.typicode.com/users/'); const originalData = await response.json(); const transformedData = originalData.map(item => { return { id: item.id, username: item.username, email: item.email, phone: item.phone, website: item.website, companyName: item.company.name, address: { addressLine1: item.address.street, addressLine2: item.address.suite, city: item.address.city, zipCode: item.address.zipcode, } } }) console.log('transformedData', transformedData); } fetchText()

As you can check and compare the returned array with the original one, the original array remains the same while the returned array is modified the way we want.

// Map - transformedData [ { "id": 1, "username": "Bret", "email": "Sincere@april.biz", "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "companyName": "Romaguera-Crona", "address": { "addressLine1": "Kulas Light", "addressLine2": "Apt. 556", "city": "Gwenborough", "zipCode": "92998-3874" } }, ... ]


Array.filter()

Array.filter does not transform the existing array, instead, it allows us to filter out values based on the condition that we set.

Similar to Array.map, it leaves the original array untouched while return us a new array which the elements passed the condition test.

async function fetchText() { const response = await fetch('https://jsonplaceholder.typicode.com/users/'); const originalData = await response.json(); const filteredData = originalData.filter(item => item.website.includes('com')) console.log('filteredData', filteredData); } fetchText()

In the above example, we want to filter out those items that the name of their website does not include "com" and return us those websites that includes.

// Filter - filteredData [ { "id": 8, "name": "Nicholas Runolfsdottir V", "username": "Maxime_Nienow", "email": "Sherwood@rosamond.me", "address": { "street": "Ellsworth Summit", "suite": "Suite 729", "city": "Aliyaview", "zipcode": "45169", "geo": { "lat": "-14.3990", "lng": "-120.7677" } }, "phone": "586.493.6943 x140", "website": "jacynthe.com", "company": { "name": "Abernathy Group", "catchPhrase": "Implemented secondary concept", "bs": "e-enable extensible e-tailers" } }, { "id": 9, "name": "Glenna Reichert", "username": "Delphine", "email": "Chaim_McDermott@dana.io", "address": { "street": "Dayna Park", "suite": "Suite 449", "city": "Bartholomebury", "zipcode": "76495-3109", "geo": { "lat": "24.6463", "lng": "-168.8889" } }, "phone": "(775)976-6794 x41206", "website": "conrad.com", "company": { "name": "Yost and Sons", "catchPhrase": "Switchable contextually-based project", "bs": "aggregate real-time technologies" } } ]


Array.forEach()

Unlike the two methods before, Array.forEach does not return us new, modified array, it actually returns us undefined. It's meant to do something for each array elements.

async function fetchText() { const response = await fetch('https://jsonplaceholder.typicode.com/users/'); const originalData = await response.json(); const processedData = originalData.forEach(item => { item['companyName'] = item.company.name delete item['company'] delete item['name'] item.address = { addressLine1: item.address.street, addressLine2: item.address.suite, city: item.address.city, zipCode: item.address.zipcode } }) console.log('processedData', processedData); console.log('originalData', originalData); } fetchText()

In the above example, for each elements in the array, we want to perform the following operations:

1)create new key-value pair (i.e. 'companyName': NEW_VALUE); 2)delete properties named 'company' and 'name'; 3)transform the property 'address' with values that we set

// ForEach - processedData undefined // originalData [ { "id": 1, "username": "Bret", "email": "Sincere@april.biz", "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "companyName": "Romaguera-Crona", "address": { "addressLine1": "Kulas Light", "addressLine2": "Apt. 556", "city": "Gwenborough", "zipCode": "92998-3874" } }, ... ]

As you can see, 'processedData' is equal to undefined while 'originalData', which is the original array, is modified.

It can be seen as an impure function, as it produces side effects and alter the original array.

Both Array.map and Array.forEach give us the same result, but in a different way. While the former returns us new array with original data untouched, the later returns us undefined and changes the original array permanently.


Array.Reduce()

Array.reduce can be a tricky one. We can transform every element like Array.map does or filter out some items which Array.filter is capable of. Furthermore, it can choose to affect or not the outer scope. According to MDN:

"TheĀ reduce()Ā method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.

So ultimately the result we get is a single value if we try to return it.

async function fetchText() { const response = await fetch("https://jsonplaceholder.typicode.com/users/"); const originalData = await response.json(); const reducedData = originalData.reduce((acc, item) => { if (item.website.includes("com")) { const currItem = { id: item.id, username: item.username, email: item.email, phone: item.phone, website: item.website, companyName: item.company.name, address: { address1: item.address.street, address2: item.address.suite, city: item.address.city, zipCode: item.address.zipcode, }, }; return [...acc, currItem]; } return acc; }, []); console.log("reducedData", reducedData); console.log('originalData', originalData); } fetchText();

In the above example, we first want to filter out those items that its 'website' does not include "com". Then we transform the structure of each item based on our needs. Finally, we use spread operator to clone the previously accumulated objects so that the newly returned array includes all the items.

// Reduce - reducedData [ { "id": 8, "username": "Maxime_Nienow", "email": "Sherwood@rosamond.me", "phone": "586.493.6943 x140", "website": "jacynthe.com", "companyName": "Abernathy Group", "address": { "address1": "Ellsworth Summit", "address2": "Suite 729", "city": "Aliyaview", "zipCode": "45169" } }, { "id": 9, "username": "Delphine", "email": "Chaim_McDermott@dana.io", "phone": "(775)976-6794 x41206", "website": "conrad.com", "companyName": "Yost and Sons", "address": { "address1": "Dayna Park", "address2": "Suite 449", "city": "Bartholomebury", "zipCode": "76495-3109" } } ] // originalData [ { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }, ... ]

So basically we are using Array.reduce to replicate the exact same result that need Array.filter and Array.map combined together to produce. Beside, it does not alter the original array that Array.filter does.

However, it could becomes hard to understand the codes if not careful enough. After all, it is a matter of choice between readability and efficiency.