You're underusing Underscore

Update 2023-09-17: I've come to regret this blog post a bit. Since ES6 the built in mechanism for these patters are good enough and generally preferable to Underscore or Lodash.

I think I was overly interested in policing easy-to-spot surface details during code review when I wrote this post and the corresponding lint rule. I highly recommend Beyond Pep8 which addresses this common disfunction.

That said, I learned a lot from writing these lint rules, so it wasn't a total loss.


For the last four months I've been reviewing every JavaScript pull request at work and simultaneously contributing to Underscore. Not surprisingly, I found my most common review comments were pointing out ways in which my colleagues could be making better use of Underscore's functionality.

Most of these comments were pointing out ways in which Underscore's API could help the developer write the same code in a simpler, more readable, less error-prone way.

As a programmer I love automating things, including my code reviews. To that end, I've written an ESLint plugin, eslint-plugin-underscore, which aims to help you spot most of these common errors.

This blog post explains the most common mistakes I found. Each mistake links to the corresponding linting rule, so you can automatically find all the instances of the mistake in your code and, hopefully, fix them.

Iteratee shorthand syntax

Many Underscore collection functions accept an "iteratee" argument that gets called on each item. To facilitate some common use cases, Underscore passes the argument through _.iteratee, which essentially means this argument is overloaded. If instead of a function, you pass a string, object, or nothing; you get a special kind of function. Here are the three types of shorthand:

Identity shorthand

Instead of passing a function that returns each value untransformed, you may omit the function argument all-together:

// BAD
_.max(scores, function (n) {
return n;
});

// BETTER!
_.max(scores);

Rule: identity-shorthand

Property shorthand

Instead of passing a function which returns a single property for each item, pass the key name:

// BAD
_.filter(users, function (user) {
return user.isAdmin;
});

// BETTER!
_.filter(users, "isAdmin");

Rule: prop-shorthand

Matcher shorthand

Instead of passing a function which tests the value of one or more property of each item, pass a "matcher" object:

// BAD
_.filter(books, function (book) {
return book.type === "hardcover" && book.avaliable === true;
});

// BETTER!
_.filter(books, { type: "hardcover", avaliable: true });

Rule: matches-shorthand

Use shorthand-specific functions

In a few cases Underscore has a special function for when you use one of these shorthand styles. In these cases, the special name is generally more descriptive:

Pluck

When you are using the property accessor syntax with _.map, instead call it _.pluck():

// BAD
var ids = _.map(posts, "id");

// BETTER!
var ids = _.pluck(posts, "id");

Rule: prefer-pluck

Where

When you are using the matcher syntax with _.filter, instead call it _.where():

// BAD
var admins = _.filter(users, { type: "admin" });

// BETTER!
var admins = _.where(users, { type: "admin" });

Rule: prefer-where

Findwhere

When you are using the matcher syntax with _.find, instead call it _.findWhere():

// BAD
_.find(post, { id: 123 });

// BETTER!
_.findWhere(post, { id: 123 });

Rule: prefer-findwhere

Compact

When you are using the "identity" syntax (passing nothing) with _.filter, instead call it _.compact():

// BAD
_.filter(suggestions);

// BETTER!
_.compact(suggestions);

Rule: prefer-compact

Manual methods

In many cases, people simply don't know that "Underscore already has a function to do that!". Here are a few examples of functions people frequently forget about, and when to use them:

Map

If you find yourself writing an _.each that just calls .push() for each item in a collection, instead you should use _.map:

// BAD
var doubled = [];
_.each(numbers, function (n) {
doubled.push(n * 2);
});

// BETTER!
var dubled = _.map(numbers, function (n) {
return n * 2;
});

Rule: prefer-map

Invoke

If you find yourself writing a _.map that calls a method on each item in a collection, instead use _.invoke:

// BAD
var upperCase = _.map(names, function (name) {
return name.toUpperCase();
});

// BETTER!
var upperCase = _.invoke(names, "toUpperCase");

Rule: prefer-invoke

Reject

If you find yourself writing a _.filter that uses an ! in its iteratee, instead use _.reject:

// BAD
var uncommented = _.filter(notes, function (note) {
return !note.hasComments();
});

// BETTER!
var uncommented = _.reject(notes, function (note) {
return note.hasComments();
});

Rule: prefer-reject

Conclusion

When using any library, the more idiomatically you can use it the better. When you can take advantage of higher-order concepts, it simplifies your code, and makes it simpler to reason about.

While I hope this article has expanded your knowledge of Underscore's API, nobody can remember everything! For those times when you forget, I hope you can incorporate eslint-plugin-underscore into your work-flow. That way I can perpetually (and automatically!) nag you about your underuse of Underscore.

Thanks

Many thanks to the engineers at Wix for eslint-plugin-lodash. My plugin began as a fork of theirs, and it gave me a great head-start.