JS Tip of the Day: Chaining

Chaining
Level: Intermediate

When method calls occur in a sequence, called off one another, this is known as chaning. You’ll often see chaining used with promises and arrays.

// chaining promises
fetch(url)
    .then(response => response.text())
    .then(text => console.log(text));

// chaining with arrays
new Array(1, 2, 3)
    .map(x => x * 2)
    .forEach(x => console.log(x));

Each method in the chain is called from the return value from the previous method. This works because each method returns a value that the next method is accessible from. Each then(), for example, returns a new promise instance from which another then() can be called. Similarly, map() returns a new array that the forEach() is able to be called from.

You too can create your own methods that support chaining. All they need to do is return a value from which additional methods can be called. The easiest way to do this is by returning this from your object methods.

class Calculator {
    constructor (value) {
        this.value = value;
    }
    add (value) {
        this.value += value;
        return this;
    }
    subtract (value) {
        this.value -= value;
        return this;
    }
    multiply (value) {
        this.value *= value;
        return this;
    }
    divide (value) {
        this.value /= value;
        return this;
    }
    logValue () {
        console.log(this.value);
        return this;
    }
}

Here is a Calculator class where this is returned for all its methods allowing every method to be chained. Each method will be called from the same instance allowing it to update (or report on) the internal value.

let calc = new Calculator(10);
calc.add(10)
    .logValue() // 20
    .divide(2)
    .subtract(5)
    .multiply(5)
    .logValue(); // 25

console.log(calc.value); // 25

Alternatively, you could have your methods return a new object instead of this allowing them to work more like promises and arrays.

class Calculator {
    constructor (value) {
        this.value = value;
    }
    add (value) {
        return new Calculator(this.value + value);
    }
    subtract (value) {
        return new Calculator(this.value - value);
    }
    multiply (value) {
        return new Calculator(this.value * value);
    }
    divide (value) {
        return new Calculator(this.value / value);
    }
    logValue () {
        console.log(this.value);
        return this; // no change, same instance
    }
}

Instead of updating an internal value, they would create new objects with the new values. This creates more new object churn, but also means your original object values are less likely to change unexpectedly.

let calc = new Calculator(10);
let newCalc = calc.add(10)
    .logValue() // 20
    .divide(2)
    .subtract(5)
    .multiply(5)
    .logValue(); // 25

console.log(newCalc.value); // 25
console.log(calc.value); // 10 (original unchanged)

Which approach you choose (if you choose to allow chaining at all) depends on how you want your objects to work.

More info: