DEV Community

Cover image for Method Chaining in JavaScript
Chinedu Orie
Chinedu Orie

Posted on

Method Chaining in JavaScript

Originally published in my blog

Method chaining is a mechanism of calling a method on another method of the same object. There are different reasons for method chaining for different people. One of the major reasons for chaining methods is to ensure a cleaner and more readable code. Let's consider an example below:

var recipeObj = new Recipe();
recipeObj.addIngredient('salt');
recipeObj.addIngredient('pepper');
recipeObj.getIngredients()
Enter fullscreen mode Exit fullscreen mode

With method chaining, the code snippet above can be refactored to:

var recipeObj = new Recipe();

recipeObj
.addIngredient('salt')
.addIngredient('pepper')
.getIngredients();
Enter fullscreen mode Exit fullscreen mode

Looking at both snippets above, one would agree that the second is cleaner than the first.

What makes method chaining possible?

What makes the method chaining possible is the this keyword. In JavaScript, the this keyword refers to the current object in which it is called. Hence, when a method returns this, it simply returns an instance of the object in which it is returned. Since the returned value is an object instance, it is, therefore, possible for other methods of the object to be called on the returned value which is its instance.

Method Chaining Example in ES5

function Recipe() {
  this.ingredients = [];
}
 Recipe.prototype.addIngredient = function (ingredient) {
   this.ingredients.push(ingredient);
   return this; // this makes chaining possible
 }

 Recipe.prototype.getIngredients = function () {
   if (!this.ingredients.length) {
     console.log('There is no ingredient in this recipe');
   } else {
     console.log(this.ingredients.toString());
   }
 }

 var recipeObj = new Recipe();

 recipeObj
 .addIngredient('salt')
 .addIngredient('pepper')
 .addIngredient('maggi')
 .getIngredients()

//salt,pepper,maggi
Enter fullscreen mode Exit fullscreen mode

Method Chaining Example in ES6

class RecipeClass {
  constructor() {
    this.ingredients = [];
  }

  addIngredient = (ingredient) => {
    this.ingredients.push(ingredient);
    return this;
  }

  getIngredients = () => {
    if (!this.ingredients.length) {
     console.log('There is no ingredient in this recipe');
   } else {
     console.log(this.ingredients.toString());
   }
  }
}

 const recipeObj2 = new RecipeClass();

 recipeObj2
 .addIngredient('garlic')
 .addIngredient('pepper')
 .addIngredient('maggi')
 .getIngredients()

//garlic,pepper,maggi
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this short article, I explained the concept of method chaining in Javascript and demonstrated how it can be achieved using the this keyword. You can give it a try. ✌️

Top comments (7)

Collapse
 
vonheikemen profile image
Heiker • Edited

If we are speaking javascript then the only thing that matters is the "shape" of the object that you return from the function.

For example, you can't chain anything to getIngredients because it returns undefined.

Here is a fun thing you can do. Recently I learned about a data type that is called Maybe. One way of creating it is this.

function Maybe(thing) {
  const nothing = {
    map: () => nothing,
    or_else: fn => fn(), // this can break the chain
    is_nothing: true 
  };

  if(thing === undefined || thing === null || thing.is_nothing) {
    return nothing;
  }

  const just = {
    map: fn => Maybe(fn(thing)),
    or_else: () => Maybe(thing),
    is_nothing: false
  };

  return just;
}

This gives you the behavior array.map but for regular objects. So you could make your functions.

function add_ingredient(name) {
  return ingredients => ingredients.concat(name);
}

function to_string(value) {
  return value.toString();
}

function render(state) {
  document.write("<pre>" + JSON.stringify(state) + "</pre>");
}

function no_ingredients() {
  render('There is no ingredient in this recipe');
  return []; // see here.
}

function you_wont_see_this() {
  // it will be attached to empty array.

  render('You will never see this, yet there is no error');
}

And now your chain can look like this.

Maybe([])
  .map(add_ingredient('salt'))
  .map(add_ingredient('pepper'))
  .map(add_ingredient('maggi'))
  .map(to_string)                // Just.map
  .map(render)                   // Just.map
  .or_else(no_ingredients)       // Nothing.or_else
  .map(you_wont_see_this);       // Array.map

See it in codepen

Collapse
 
kozakrisz profile image
Krisztian Kecskes

I remember when I tried to solve this chaining thing some years ago. It was full of mistery. I think this is the reason why beginners shout start with the basics things aka reference and primitive types, contexts, closure etc... Thanks for the clear article. It can be useful for a lot of people.

Collapse
 
nedsoft profile image
Chinedu Orie • Edited

You're perfectly right Krisztian, some concepts are better and more naturally understandable with time. At some point, I used to battle with understanding chaining both in PHP but now, understanding it is just seamless. Thank you.

Collapse
 
shreyansh_zazz profile image
Shreyansh Zazz • Edited

How can we decompose this into multiple classes? @nedsoft

Collapse
 
nedsoft profile image
Chinedu Orie

I think it depends on what you want to achieve, you could split it to multiple classes and have them inherit each other depending on what you want to achieve

Collapse
 
shreyansh_zazz profile image
Shreyansh Zazz

I have a Validator class which is nothing but the base class having methods like validate(), and some root properties which will be needed for processing. Then we will have different classes like Type Validator which will have methods validate types like isString, isInt, etc. Then we will have classes like DB Validator which will have methods like isDuplicateUser, etc.

In order to use this Validator I will import and call chained methods like Validator().isString().isDuplicate().validate("username") @nedsoft

Collapse
 
tawaliou profile image
Tawaliou

Very useful!