3 lodash functions you should be using in your Javascript

Use these three functions to make your code more declarative and easier to reason about.

1. Chain iterations with .chain()

Let's take the example of a function that takes an array of filters and evaluates a row. If the filters include the row, we return true and if not, false. See how long it takes you to figure out what we're doing in the method.

Before

function(filters /*Array*/, row /*Object*/){  
  var convertedFilters = _.map(filters, convertFilter)
  var validatedFilters = _.map(convertedFilters, validateFilters)
  var filterResults   = _.map(validatedFilters, f => applyFilters(f, row))
  var combinedResults = combineResults(filterResults)
  var evalatedReults = evaluteResults(combinedResults)
  return evaluatedResults
}

Notice the problems? Likely, it took you some work to follow the flow. You have to read each line carefully to figure out what steps are happening in which order. If you were to rename a function, you should probably rename the variable that you're assigning the value to. If you rename the variable, you have to rename all the places you use it. If we chain them together, we can clarify the code:

After

function(filters, row){  
  return _(filters)
          .map(convertFilters)
          .map(validateFilters)
          .map(f => applyFilters(f, row))
          .thru(combineResults)
          .thru(evaluateResults)
          .run() // .run() Kicks off the chained function, otherwise we're left with a wrapped lodash instance.
}

Even without knowing the shape of the filters variable, you can infer that we need to first convert them and validate them before we apply them. That logic wasn't easily apparent in the before example and reveals that we might want to consider how we respond to invalid filters or filters that don't convert correctly. Because we used .flow, we had better insight into our implementation's weaknesses and strengths.

Because of lodash's shortcut fusion, intermediate arrays aren't made which means better performance and less GC pressure.

2. Chain functions together with _.flow

While _.map and friends are great for arrays/hashes, what if we want to create flows of data where we want to pass the output of one method into the input of another? For that, we have .flow. Take the following code:

Before

function validateFilter(filter, acceptableOperators){  
  var validatedBlanks = this.validateBlanks(filter)
  var validatedValues = this.validateValues(validatedBlanks)
  var validatedOperators = this.validateOperators(acceptableOperators, validatedValues)
  var validatedLogicalOperators = this.validateLogicalOperators(validatedLogicalOperators)
  return validatedLogicalOperators
}

Notice how the function is hard to follow? It's littered with variables. Each variable is named like the function that it's associated with which means renaming a function requires renaming the variables. Take validateBlanks. Those words appear as a function name, validateBlanks and then as a variable validatedBlanks (in the past tense) and then again when it's passed to validateValues. While each variable is only used once, if I were a developer coming into the code, I wouldn't know that. I'd have to carefully read through the function and make sure I could change a line without affecting other lines. We can do better.

First, let's use _.flow to take out all the variables. _.flow passes the output of the first function into the input of the second function and the output of the second to the input of the third, etc. Here, we create a flow and kick it off by passing it filter.

After

function validateFilter(filter, acceptableOperators){  
  return _.flow(
    this.validateBlanks,
    this.validateValues,
    this.validateOperators.bind(this, acceptableOperators),
    this.validateLogicalOperators,
  )(filter)
}

Already, it's far easier to see what the function is doing. We can quickly see when the acceptableOperators param is being used. In the original code, it wasn't immediately clear when (or if) all the arguments were being used.

3. Make _.flow tastier with _.curry

We can make the code nicer by taking out .bind(this, ...) and wrap the function with _.curry. _.curry allows us to call the function with all of it's arguments at once (e.g. validateOperators(operators, filter) or one by one validateOperators(operators)(filter). My favorite part of _.curry is it's transparency -- I can add curry and another developer can call the function without having to know I curried it. Granted, this can cause odd behavior if the developer doesn't pass the function all of it's arguments (the function would return a lodash instance). I address this by making sure the team knows that if they call a function and get a wrapped lodash instance back (instead of the real result), they need to pass the method all of it's arguments.

validateOperators: _.curry(function(acceptableOperators, filter){  
  // Do stuff
})

Let's add in the curried function. While we're at it, let's add in new validations:

After (with _.curry)

// Notice I changed the order of the functions and added a new one:
function validateFilter(filter, acceptableOperators){  
  return _.flow(
    this.validateLogicalOperators,
    this.validateComparators,
    this.validateOperators(acceptableOperators),
    this.validateBlanks,
    this.validateValues,
  )(filter)
}

See how easy this is to read? We added new validation steps without having to start a cascade of renaming. Easy!

Scott Messinger

Read more posts by this author.