In this example, I’ll show you how to add helper methods to your classes with little effort using prototyping. As a bonus approach leaves you with no messes to clean up later.

I recently dived back into Node and started writing a portfolio project. I ended up wanting to use file caching for the project but needed to be able to expire the caches, which the file-system-cache project does not support.

I wrote some helper functions for it, which — granted — I could call from inside my code. But instead, I decided to use prototyping to add the methods to the classes themselves and call them from the inside.

// prototypeProperties.js
function prototypeProperties(destClass, propObj) {
  Object.keys(propObj).forEach(function (name) {
    destClass.prototype[name] = propObj[name];
  });
}

export default prototypeProperties

I was looking for a way to mimic what PHP does with traits, and with the method above, I’ve achieved that. It takes an uninitialized class (destClass) and an object of properties to add to that class (propObj) as input.

My main goal with the implementation was to mimic what PHP does with traits. From the point of view of OOP, javascript still has some way to go. Dependency Injection aside, effortlessly adding helper functions to your classes without dealing with inheritance issues is very useful.

// helpers.js
const getCache = async (cacheKey) => {
  let cached = await cache.get(cacheKey)
  if (cached) {
    const now = new Date();
    if (cached.expiresAt <= now.getTime()) {
      return false;
    }

    return JSON.parse(cached.content);
  }

  return false;
}

const setCache = async (cacheKey, content, expiresIn = undefined) => {
  const now = new Date()
  const cacheExpiresIn = expiresIn || 60
  const status = await cache.set(cacheKey, {
    'expiresAt': now.setSeconds(now.getSeconds() + cacheExpiresIn),
    'content': JSON.stringify(content)
  })
  return status;
}

const helpers = { getCache, setCache }

These are some cache methods I needed to add TTL to my file cache implementation, and as you can see, the methods are fairly self-explanatory. A getter checks if the cache exists or has expired, and a setter stores the cache plus the expiration time as an object.

Both methods are stored in a helper object and obviously exported in the complete code snippet. With this object, we can continue using our previous prototyping method.

// DogApi.js
prototypeProperties(DogApi, helpers)

The method is called before exporting the class, and the methods are now available on the DogApi class.

So when should we actually use prototyping? Use cases include adding functionality to classes you either can’t or won’t extend. It’s a clean way of adding new global functionality, such as global helper functions, to existing code without causing too much mess to clean up later.

I don’t like dealing with deep inheritance hierarchies in my code, though obviously, class inheritance has its place.

PHP has the ability to see traits, so if you have a trait called Cachable, you can check if classes have this trait. But obviously, we don’t have that option in Node, but we can check for undefined to see if a method is present, so we could use this to check if an object has a method or property.

What are your thoughts on using prototyping in this way? Do you prefer other approaches to solve the same issues? Let me know in the comments.