Chaining method calls in JavaScript

By

Learn how to chain method calls in JavaScript like car.start().drive() by returning this from each method, and why arrow functions break the pattern.

~~~

You can chain method calls in JavaScript by returning this from each method. Every call then hands the object back, ready for the next call.

The result looks like this:

car.start().drive()

It’s pretty convenient to do so.

Instead of writing

car.start()
car.drive()

we can simplify in a one-liner.

How does chaining work?

JavaScript evaluates the chain left to right. car.start() runs first, and whatever it returns becomes the object drive() is called on.

So chaining works if each method returns the object itself. In other words, the implementation must be something like this:

const car = {
  start: function() {
    console.log('start')
    return this
  },
  drive: function() {
    console.log('drive')
    return this
  }
}

Inside each method, this points to car, so returning it gives the next call the same object to work on.

The same pattern works in a class. Return this from each method and instances become chainable:

class Car {
  start() {
    console.log('start')
    return this
  }

  drive() {
    console.log('drive')
    return this
  }
}

new Car().start().drive()

You already use a similar idea with strings and arrays. A call like .map().filter() chains because each method returns a new array. The difference is that our car methods return the same object, mutated, rather than a new value.

Why arrow functions break the pattern

Note that you can’t use arrow functions here, because this in an arrow function used as an object method is not bound to the object instance. It picks up this from the surrounding scope, so return this would return the wrong thing.

I like to use arrow functions all the time, and this is one of the cases where you can’t.

The pitfall: forgetting to return this

If one method in the chain forgets the return this line, it returns undefined, and the next call in the chain blows up:

const car = {
  start: function() {
    console.log('start')
  },
  drive: function() {
    console.log('drive')
    return this
  }
}

car.start().drive()
// TypeError: Cannot read properties of undefined (reading 'drive')

The error points at drive, but the real bug is in start(). Keep that in mind when you debug a broken chain: check what the previous method returns.

Chained method calls are great when you are not returning a set of values from the method. If a method needs to return data, you have to assign the result to a variable, and chaining stops there:

const result = car.start()
if (result) {
  car.drive()
}

A method can return this or a meaningful value, not both. Decide which job each method has.

~~~

Related posts about js: