How to Use Decorators in JavaScript

shape
shape
shape
shape
shape
image

How to Use Decorators in JavaScript

(Comprehensive Step-by-Step Guideline)

Last night I was working on a large-scale project for one of my clients. Following the defined task, I was creating a couple of API calls to my server. When the project scales, the size of the code gets bigger, the new problems start taking place. The backend was throwing various errors based on API responses, and in the frontend, for handling this error, I had to wrap this function inside try and catch. To know more about this, go through this link.

I Googled for a solution to write code in a better way, and fortunately, I come up with a solution in JavaScript, also known as decorators.

So by the end of this blog, you will learn;

  1. We will learn what decorators are?
  2. Why do we use decorators?
  3. What are targets, names, and descriptors in decorators?
  4. We will write our own custom @TryCatch decorator

What are Decorators?

In a layman’s term, decorators are a piece of code that wraps with another functional composition or higher-order functions — literally ‘decorating’ it. This is a very basic higher-order function. CustomHigherOrderFunction takes a function as an argument and returns a wrapper of that function, which will append a decorator string in front of the passed string (hello world). So the output of the following function will be “​Decorator hello world.”

function customHigherOrderFunction(fn) {
  return function (name) {
    const customString = `Decorator ${name}`;
    fn(customString);
  }
}

function sayName(name) {
  console.log(name);
}

var higherOrderFunction = customHigherOrderFunction(sayName);
higherOrderFunction('hello world');

Why do we use decorators?

The decorator makes use of another function to poop out code. There is no need for any fancy decorators here! However, the entry of ES2015 Classes is a little strange. To wrap a class, and class Member decorators in KJavaScript seems like a very cool choice!

Decorating a Class Method

For using decorator in javascript, we will have two types of syntax

  1. @decoratorName
  2. @decoratorName(arguments)

So before creating a decorator, we have three main concepts of decorators that are;

  1. Target: The Object in which you’re doing some decorating. Here it is your Class
  2. Name: The name string you’re decorating.
  3. Descriptor: An object to the decorated method through the value property.
class ApiService {
  async fetchTodos() {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos');
      const result = await response.json();
      console.log(result);
      // Do some cool stuff here
    } catch (err) {
      // Handle error here
      console.log(err);
    }
  }
}
const instance = new ApiService();
instance.fetchTodos();

Here I have made a class to fetch some data from the backend, and for error handling, I have wrapped inside try-catch. But the problem here is I have to wrap this function inside try-catch every time we add new API endpoints. So let’s change it to decorators

class ApiService {
  @TryCatch()
  async fetchTodos() {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos');
    const result = await response.json();
    // Do some cool stuff here
    return result;
  }
}
const instance = new ApiService();
instance.fetchTodos();

As we can see here, we have made APIService class, which contains fetchTodos method and fetchTodos function to fetch todos from the server. Still, the significant thing to note down here is before method we have one decorator that is @TryCatch(), and here actual magic happens. So let’s see what actually contains inside TryCatch.

function TryCatch() {
  return function (target, name, descriptor) {
    var fn = descriptor.value;
    descriptor.value = async (...args) => {
      try {
        const response = await fn.apply(this, args);
        return response;
      } catch (err) {
        // Handle error here
        console.log(err);
      }
    }
  }
}

In this function, as we can see, the first argument is a target, which is here ApiService class; the second argument is the name that is here fetchTodos string, and the third argument is the descriptor. Most of the time, we will deal with the descriptor. So first, we will take descriptor.value and store it in a variable here as fn. Then we will override descriptor.value with async function, and which takes args as parameters and wrap inside try to catch inside try block we will call fn.apply(this, args) and return the response. If any error occurred, then it is advisable to go inside the catch block. So this is it for making a TryCatch decorator. Below you can find the complete code for this tutorial.

function TryCatch() {
  return function (target, name, descriptor) {
    var fn = descriptor.value;
    descriptor.value = async (...args) => {
      try {
        const response = await fn.apply(this, args);
        return response;
      } catch (err) {
        // Handle error here
        console.log(err);
      }
    }
  }
}
class ApiService {
  @TryCatch()
  async fetchTodos() {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos');
    const result = await response.json();
    // Do some cool stuff here
    return result;
  }
}
const instance = new ApiService();
instance.fetchTodos();

Wrapping Up

In case if you are looking for skilled JavaScript developers, to implement many of such out-of-the-box functionalities, then hire the best JavaScript developers from us. We have top-tier JavaScript developers, who have in-depth knowledge and top-of-the-line expertise in services all the shapes and sizes of clients. I hope your purpose of landing on this blog post has been served. In case of doubt or question, feel free to get in touch in the comments section below. We are open to learning more about the same from you and your experiences.

Happy Coding :)