December 1, 2020•757 words
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 actually 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 possible 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 which 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 flatenning 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.