Ricardo Tavares

October 25, 2022

Is the reduce method in JavaScript hard to review?

One of the best things about JavaScript nowadays is that you can just type "MDN" next to whatever you want to know in your search box and you will get very nice documentation from the Mozilla Developer Network. However, if you go through Array.prototype.reduce(), your initial impression may be "I guess I understand how it works, but what is this good for? Adding numbers, I guess?" So let's take a quick overview of how to reduce an array. Let's say I have a bunch of errors that I want to display in an alert, for example.

let errors = ['File size too big', 'Headers not found', 'Invalid identifier in column B'];
let message = 'Errors found:';
message = errors.reduce(
    (text, error) => text + "\n" + error, 
    message
);
alert(message);
(Quick note about browser compatibility that I think every HTML/CSS/JS article needs to mention: yes, you can use reduce arrays in IE and maybe Android 4.4, no you can't write code in arrow functions for those browsers. However, Microsoft has moved on from IE, so I think we should as well.)

Reduce is a way for you to build one thing out of several things. It's a method you can call on your arrays by giving reduce a function that will iterate through them and a value that can be updated along with that iteration. So, a less debuggable version of this code could be:

alert(['File size too big', 'Headers not found', 'Invalid identifier in column B'].reduce(
    (text, error) => text + "\n" + error, 
    'Errors found:'
));

As I occasionally come back to code that has used reduce, what I think makes it a bit hard to read is the order through which the code seems to flow. You start from the ending, then you go back to check what function parameter carries the value and then you go forward again to see how it gets returned. You can use a named function, but I'm not sure if this seems more readable:

let errors = ['File size too big', 'Headers not found', 'Invalid identifier in column B'];
let message = 'Errors found:';
let reduceErrors = (text, error) => text + "\n" + error;
message = errors.reduce(reduceErrors, message);
alert(message);

For this example, since the returning value is just a string, we could instead use the join method to put together our message...

let errors = ['File size too big', 'Headers not found', 'Invalid identifier in column B'];
let message = "Errors found:\n" + errors.join("\n");
alert(message);

...which seems to be what we wanted from the start, right? So, what can justify the added complexity of the reduce method? If, for example, there were errors we didn't want in the message, we could simply 
errors.filter(error=>error.length<20).join("\n") 
so reduce is probably more about still including every item while handling each one in possibly different ways. So, if we wanted to make the file errors stand out, we could, for example:

let errors = ['File size too big', 'Headers not found', 'Invalid identifier in column B'];
let message = 'Errors found:';
let reduceErrors = (text, error) => text + "\n" + (error.toLowerCase().indexOf('file') > -1 ? error.toUpperCase() : error);
message = errors.reduce(reduceErrors, message);
alert(message);

And perhaps the order in which the information seems to flow can become irrelevant if the function that you pass to reduce can come from anywhere. This theoretically can be any function that takes an A together with some specific B and then returns A according to B. Let's imagine that our example is about loading some Excel file into the browser and that some errors can be found in specific columns. Our array of errors can also result from reducing all those columns into some list of possible issues found in them. And, of course, different functions can be applied to check for these. From these assumptions, we could have some code like:

let errors = [];
errors = files.reduce(checkFileFormat, errors);
errors = columns.reduce(findInvalidHeaders, errors);
errors = columns.reduce(checkIdentifiers, errors);

This looks both more useful and maybe easier to follow. Finally, MDN itself has a lot of useful examples that you probably want to go through, namely for flattening an array of arrays or for replacing filter and map with simply reduce. It's an invaluable resource for anyone who works with JS and I will probably visit some other topic from them in the future. See you soon.

(Originally written on December 1, 2020)

About Ricardo Tavares

Creates things with computers to understand what problems they can solve. Passionate for an open web that anyone can contribute to. Works in domains where content is king and assumptions are validated quickly.

Mastodon  |  Twitter  |  GitHub


View From the Web