Functional Array Operations in JavaScript

September 22nd 2017 JavaScript TypeScript

JavaScript's Array prototype provides a comprehensive selection of methods which lend themselves nicely to functional programming approach, such as map and reduce. Surprisingly, when doing code reviews I often see that developers are not aware of them or not used to using them. In this post, I'll do an overview of these functions with examples how they can simplify your code.

Find

The find method will return the first element in the array, which matches the supplied predicate function, or undefined if no such element exists.

It can be used to simplify code like this:

for (let i = 0; i < array.length; i++) {
  if (array[i].id === selectedId) {
    return array[i];
  }
}
return undefined;

The functional equivalent is a one-liner:

return array.find(function(element) {
  return element.id === selectedId;
});

For .NET developers, this method behaves the same as FirstOrDefault in LINQ.

Filter

A very similar method is filter. It will return all the matching elements instead of just the first one.

It can be used in the following situation:

let completedElements = [];
for (let i = 0; i < array.length; i++) {
  if (array[i].status === 'complete') {
    completedElements.push(array[i]);
  }
}

We can achieve the same using the following:

let completedElements = array.filter(function(element) {
  return element.status === 'complete';
});

The LINQ equivalent is Where.

Map

When you want to transform or project the elements in the array, you can use map.

The non-functional sample to simplify:

let elementsWithDistance = []
for (let i = 0; i < array.length; i++) {
  let elementWithDistance = {
    id: array[i].id,
    name: array[i].name,
    distance: calculateDistance(currentLocation, array[i].location)
  };
  elementsWithDistance.push(elementWithDistance);
}

Equivalent code using map:

let elementsWithDistance = array.map(function(element) {
  return {
    id: element.id,
    name: element.name,
    distance: calculateDistance(currentLocation, element.location)
  }
});

In LINQ, the same can be achieved with Select.

Some

To check whether any items in the array match a condition, some can be used.

We could hand-code it like this:

let contains = false;
for (let i = 0; i < array.length; i++) {
  if (array[i].id === selectedId) {
    contains = true;
  }
}

Or just use some instead:

let contains = array.some(function(element) {
  return element.id === selectedId;
});

In LINQ, Any does the same.

Every

When we want to make sure that all elements meet a condition, we will use every.

We could achieve that with the following code:

let done = true;
for (let i = 0; i < array.length; i++) {
  if (array[i].status !== 'complete') {
    done = false;
  }
}

Using every instead would be simpler:

let done = array.every(function(element) { element.status === 'complete' });

All can be used in LINQ in such cases.

Reduce

The reduce function is useful when we want to aggregate all of the array into a single value, e.g. we are looking for the smallest element.

This is how we could find the closest element:

let closestElement = undefined;
for (let i = 0; i < array.length; i++) {
  if (closestElement === undefined || array[i].distance < closestElement.distance) {
    closestElement = array[i];
  }
}

Using reduce in such cases might seem a little unintuitive until you get used to it:

let closestElement = array.reduce(function(a, b) {
  return a !== undefined && a.distance <= b.distance ? a : b;
}, undefined);

The corresponding function in LINQ is called Aggregate.

You should check, in which browsers each method is supported before you start using it. In same cases you might need to use polyfills or a transpiler.

Copyright
Creative Commons License