Chaining Patterns in JavaScript

Christopher T.

January 25th, 2022

Share This Post

As developers it is our responsibility to write good code. This means writing code that is both efficient and readable. But sometimes we come across obstacles where we have to decide which code is better to go with when one sacrifices readability to perform better or vice versa.

This post will go over different chaining patterns in JavaScript and I hope that this will help you in any way possible when it comes to writing your chaining operations.

Method Chaining

In JavaScript, method chaining is when methods are invoked from one object to another without creating intermediate variables. In otherwords it is a single statement of multiple method invocations which we instruct our program to perform.

Jquery is a good example of taking great advantage of its semantics because of its elegant ability to chain together its commands while encapsulating DOM APIs efficiently. It uses clear and concise syntax:

$(‘#main’).css(‘background’, ‘red’).height(200).css(‘text-align’,’center’).width(500);

This is an example of a single statement. In one line this does all of these in a single execution:

  1. Queries for a DOM element with id main
  2. Changes the background color to red
  3. Changes the height to 200px
  4. Changes text-align to center
  5. Changes the width to 500px

Another popular JavaScript library, expressjs utilizes method chaining to provide an easy and robust API for developers to use.

Here is an example taken from their error handling page:

var bodyParser = require('body-parser')
var methodOverride = require('method-override')

app.use(
  bodyParser.urlencoded({
    extended: true,
  }),
)
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

The logic is hidden within the implementation and returns this to allow method chaining to continue:

app.use = function use(fn) {
  var offset = 0
  var path = '/'

  // default path to '/'
  // disambiguate app.use([fn])
  if (typeof fn !== 'function') {
    var arg = fn

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0]
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1
      path = fn
    }
  }

  var fns = flatten(slice.call(arguments, offset))

  if (fns.length === 0) {
    throw new TypeError('app.use() requires a middleware function')
  }

  // setup router
  this.lazyrouter()
  var router = this._router

  fns.forEach(function (fn) {
    // non-express app
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn)
    }

    debug('.use app under %s', path)
    fn.mountpath = path
    fn.parent = this

    // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err)
      })
    })

    // mounted an app
    fn.emit('mount', this)
  }, this)

  return this
}

code-block-writer makes it easy to write code with code with (yet again) the method chaining api:

const writer = new CodeBlockWriter()

writer.write('class MyClass extends OtherClass').block(() => {
  writer.writeLine(`@MyDecorator(1, 2)`)
  writer.write(`myMethod(myParam: any)`).block(() => {
    writer.write('return this.post(').quote('myArgument').write(');')
  })
})

The Builder Pattern

The Builder Pattern by nature uses method chaining to achieve its goal. The builder by definition is a creational design pattern that lets you construct complex objects in a step by step fashion, effectively simplying the process.

Sounds pretty similar to how method chaining is described isn't it?

If we were to create a Chicken class where its methods are to construct all of its body parts, we can use the builder to construct a full chicken:

const initBodyParts = () => ({
  beak: null,
  breast: null,
  claw: null,
  comb: null,
  eye: { left: null, right: null },
  eyeLobes: { left: null, right: null },
  mainTail: null,
  thigh: { left: null, right: null },
  toe: { left: null, right: null },
  wing: { left: null, right: null },
})

class ChickenEye {
  constructor(options) {
    this.options = options
  }

  close() {
    //
  }
}

class Chicken {
  constructor(bodyParts) {
    this.bodyParts = bodyParts
  }

  closeEyes() {
    this.bodyParts.eye.left.close()
    this.bodyParts.eye.right.close()
  }
}

class ChickenBuilder {
  constructor() {
    this.bodyParts = initBodyParts()
  }

  addComb(options) {
    this.bodyParts.comb = options
    return this
  }

  addBeak(options) {
    this.bodyParts.beak = options
    return this
  }

  addBreast(options) {
    this.bodyParts.breast = options
    return this
  }

  addToe(options) {
    this.bodyParts.toe[options.side] = options
    return this
  }

  addClaw(options) {
    this.bodyParts.claw = options
    return this
  }

  addEye(options) {
    this.bodyParts.eyes[options.side] = new ChickenEye(options)
    return this
  }

  addEarLobes(options) {
    this.bodyParts.earLobe[options.side] = options
    return this
  }

  addMainTail(options) {
    this.bodyParts.mainTail = options
    return this
  }

  addWing(options) {
    this.bodyParts.wing[options.side] = options
    return this
  }

  addThigh(options) {
    this.bodyParts.thigh[options.side] = options
    return this
  }

  build() {
    const chicken = new Chicken(this.bodyParts)
    this.bodyParts = initBodyParts()
    return chicken
  }
}

const chickenBuilder = new ChickenBuilder()
const chicken = chickenBuilder
  .addBeak({ shape: 'round' })
  .addComb()
  .addEye({ side: 'left' })
  .addEye({ side: 'right' })
  .addThigh({ side: 'left' })
  .addThigh({ side: 'right' })
  .addWing()
  .addMainTail({ length: 2 })
  .addToe({ side: 'left' })
  .addToe({ side: 'right' })
  .build()

chicken-using-javascript-chicken-builder-design-pattern-method-chaining

If we didn't use a builder the code could become repetitive and it's not clear to consumers whether they return values or not which forces them to look for some documentation:

const chickenBuilder = new ChickenBuilder()
chickenBuilder.addBeak({ shape: 'round' })
chickenBuilder.addComb()
chickenBuilder.addEye({ side: 'left' })
chickenBuilder.addEye({ side: 'right' })
chickenBuilder.addThigh({ side: 'left' })
chickenBuilder.addThigh({ side: 'right' })
chickenBuilder.addWing()
chickenBuilder.addMainTail({ length: 2 })
chickenBuilder.addToe({ side: 'left' })
chickenBuilder.addToe({ side: 'right' })
const chicken = chickenBuilder.build()

If you've never used a builder before, take a look at this example below and see if you can spot the nice benefit it provides to us:

Without builder

let baseUrl = 'https://frogs.com'
let url = `${baseUrl}`

const pathname = `/api/v1`
const page = 2
const filter = 'date_added'
url = url + pathname + page + filter

With builder

let baseUrl = 'https://frogs.com'
let url = `${baseUrl}`

const builder = new UrlBuilder(baseUrl)
url = builder.page(2).filter('date_added').pathname('/api/v1').build()

By using the builder we saved multiple lines of code but we also hide the implementation details from the consumer. Hiding the implementation is whats important here.

This is the implemetation of the builder from the above snippet (yes I know it is long, but keep listening):

class UrlBuilder {
  #filter = ''
  #page = ''

  constructor(baseUrl = '') {
    this.baseUrl = baseUrl
  }

  pathname(value) {
    this.pathname = value
    return this
  }

  page(value) {
    this.#page = value
    return this
  }

  filter(value) {
    this.#filter = value
    return this
  }

  build() {
    const append = (str = '', key = '', value) => {
      if (!str.includes('?')) str += '?'
      str += `&${key}=${value}`
      return str
    }

    let url = `${this.baseUrl}/api/v1/${this.pathname}?`

    this.#page && (url = append(url, 'page', this.#page))
    this.#filter && (url = append(url, 'filter', this.#filter))

    return url
  }
}

The thing to take from this is that as developers our job is to create the efficient and readable code.

Yes our example without the builder was technically shorter, but is it reusable? Will we be able to work with people who can read the code as easily as we can (we wrote it).

The answer to both is no. When we share our code (or even when we try to reuse our code) we can easily pick up the builder and re-use it, as opposed to the earlier one. That is because we have to copy and paste the code if we were to try to re-use the earlier one.

When we copy and paste, we produce duplicate code. When code becomes duplicated, we have to make double the changes when we must writer updates to our api. And when we unnecessarily have to make double or triple the changes, our code becomes unmaintainable. The builder pattern is a simple but powerful pattern that solves all of those issues in one swoop!

The Chain of Responsibility Pattern

The Chain of Responsibility (COR) is a pattern that allows some request to be sent, received, and handled by multiple objects. These objects (which are just functions) are not dependent on the implementation details of the previous nor the next request and can decide what to do when it runs its execution. They can also either abort the whole chain or decide to let the request continue on to the next object (or function) in the chain.

Here is an example of the pattern used in DOM:

<div id="root" onclick="onBtnContainerClick()">
  <button>Say hello</button>
</div>

There's not really an "official" or correct way to implement this pattern as long as the functions can chain one after another in a controllable and predictable way.

ExpressJS also uses this pattern as well in their router to pass middleware handlers from one handler to the next:

app.get(
  '/user/:id',
  function (req, res, next) {
    // if the user ID is 0, skip to the next route
    if (req.params.id === '0') next('route')
    // otherwise pass the control to the next middleware function in this stack
    else next()
  },
  function (req, res, next) {
    // send a regular response
    res.send('regular')
  },
)

But if you're wondering how exactly handlers get linked to eachother by next() calls, it works exactly like how a linked list data structure works.

There's no right way to implement this pattern in practice but here is an example that links handlers together:

class Fighter {
  constructor() {
    this.hp = 100
    this.next = null
  }

  fight(target) {
    target.hp -= 25
    this.next && this.next.fight(target)
  }
}

class FistFighters {
  constructor() {
    this.fighters = []
  }

  addFighter(fighter) {
    if (this.fighters.length) {
      this.fighters[this.fighters.length - 1].next = fighter
    }
    this.fighters.push(fighter)
  }

  fight(target) {
    this.fighters[0].fight(target)
  }
}

const mob = new FistFighters()

const joe = new Fighter()
const michael = new Fighter()
const jacob = new Fighter()
const mojo = new Fighter()

mob.addFighter(joe)
mob.addFighter(michael)
mob.addFighter(jacob)
mob.addFighter(mojo)

const jim = new Fighter()

mob.fight(jim)

console.log(jim) // He died because his hp is down to 0 because the mob chained michael, jacob, mojo

example-of-chain-of-responsibility-pattern-in-practice-in-javascript

Optional Chaining

Accessing nested objects is a dangerous operation because if our code doesn't handle when values are empty or simply whose prototype isn't inherited from the object prototype we can be thrown a TypeError which can crash our program.

Here is what I mean:

const buckets = {
  red: {
    name: 'RedBucket',
    items: [1, 2, 10],
  },
}

const redBucketItems = buckets.red.items[1]

If we tried to access buckets.red.items[1] when items is accidentally set to a non-object like data type (like a null value), we would get something unpleasant that looks like this:

Uncaught TypeError: Cannot read properties of null (reading '1')

JavaScript provides a way to make this easier called optional chaining which simplifies accessing values through objects that are connected which at some point might become null or anything other than objects:

buckets.red.items[1]

// to:

buckets?.red?.items?.[1] // Result: undefined   (but no crash!)

Promise Chaining

function fetchFrogs() {
  return new Promise((resolve, reject) => {
    return fetch('https://frogs.com/api/v1')
  })
}

By chaining promises together you can write asynchronous code where each of your functions will be promised (literally) to receive their data in time:

function callMeWhenYouGetMyFrogs(frogs) {
  window.alert(
    `Here is a list of frogs fetched: ${JSON.stringify(frogs, null, 2)}`,
  )
}

fetchFrogs()
  .then((response) => response.json())
  .then((data) => data.frogs)
  .then((frogs) => callMeWhenYouGetMyFrogs(frogs))
  .catch(console.error)

Conclusion

And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!


Top online courses in Web Development

Tags


Read every story from jsmanifest (and thousands of other writers on medium)

Your membership fee directly supports jsmanifest and other writers you read. You'll also get full access to every story on Medium.

Subscribe to the Newsletter

Get continuous updates

Mediumdev.toTwitterGitHubrss

© jsmanifest 2023