When some operation on an array needs to be performed, the first reaction might be to use some for loop, while loop, or similar structure. However, using loops in this manner can lead to some difficulties when it comes to debugging and code maintenance; it can also make the code more verbose than needed.
Alternatively, array methods can be directly utilized. In Javascript and many other languages, there are methods such as "forEach", "map", "filter", and "reduce". In other languages, they may appear as "select", "where", or similar, but the underlying concepts are the same. Some of the advantages of using these methods over looping are:
- Easier to read: code with these methods is less verbose and can more easily be understood.
- More declarative: instead of 'controlling' how to handle the array, these methods take care of the work behind the scenes. You only need to specify what should happen for each item in the array.
- Easier to maintain: it will be easier to determine the intent of the original developer. For ExampleJs, if the developer uses 'map', it will be clear they wanted to create a new array based on some manipulation of each element in the old array with a 1 to 1 correspondance.
- Composable: these methods can be chained together to compose a larger operation over an array, as opposed to mixing all of the logic within some loop.
ForEach
- Purpose: to perform some operation using each item in the array.
- Callback Function:
- Input: element of the array
- Return: none. Any returned value will be ignored.
In the loggingService ExampleJs above, the service will make the async call and not handle the response. In any case where the response is needed, forEach would not be the ideal choice to handle the response.
Filter
- Purpose: to get a subset of the original array based on some filtering logic.
- Callback Function:
- Input: element of the array
- Return: true if the element should be included in the new array, false otherwise
Find
Find is equivalent to getting the first element of a filter with the optimization that the operation stops after the first item is found.
- Purpose: to find the first element of an array which satisfies some filtering logic.
- Callback Function:
- Input: element of the array
- Return: true if the element matches, false otherwise.
Map
- Purpose: to create a new array based on some modification of items in the original array.
- Callback Function:
- Input: element of the array
- Return: corresponding element of the new array
Reduce
- Purpose: to create some new object based on the accumulation of values in the original array.
- Callback Function:
- Input: accumulator value, element of the array
- Return: accumulated value with updates from the input element
- Initial Value: optional parameter:
- If this value is provided, reduce will start with the first element in the array, and acc will start with that initial value.
- If this value is not provided, reduce will start with the second element in the array, and acc will start with the first element.
A Note on Streams
In the JavaScript examples above, the methods were invoked directly on the array objects, but in other languages, a stream was used. The output from each is the same, but in the case of stream, the expressions are executed differently. For example, if you chain a filter, then map, a stream will apply the filter, then map, callbacks for each item in the stream, whereas JavaScript will apply the filter on all elements, then the map on the elements not filtered. This has implications on performance depending upon the circumstances, but we will not go into detail on this here.
Asynchronous Versions
When working with Javascript, it can be useful to operate with asynchronous versions of these methods. For example: we may want to make some backend call for each item in an array. Javascript's Array object does not provide functionality for this by default, but there are multiple ways of achieving this, or similar, functionality:
- Use Loop Constructs: using async/await, we can handle asynchronous tasks within loops: for loop, while loop, etc. This approach will work but is not an example of functional programming.
- Implement Your own Functions: you can always implement your own functions to handle async map, filter, etc.
- Leverage a Library Which Implements It: many libraries include async versions, including one we've created.
We have created @byte-this/funscript, a lighweight library we've created and use ourselves, which provides implementations for common functional patterns.