Tigran Bayburtsyan Cofounder and CTO at Hexact, Inc. Software scalability expert!

New decorators proposal

3 min read 910

New Decorators Proposal

JavaScript was never designed to be a classic OOP programming language, which was the most common programming ideology when it was first developed. It was meant to be a plain scripting language with a few features, such as functions and JSON object structures. There was always that hack that forced a JavaScript function to act as a class, but this was more of a workaround than a deliberate design.

Today, JavaScript runs on virtually all software platforms and is one of the world’s most popular and universal programming languages. For the past few years, the ECMAScript community has been shaping what was once a very simple language into a more robust one to help JavaScript maintain its dominance. As a result, we now have full-featured classes and can do much more than we ever imagined possible with JavaScript.

Decorators represent one of the most important steps in this evolution, and they are crucial to keeping JavaScript competitive with languages such as Java, Python, C#, etc. Let’s take a closer look at decorators and see how we can use them to get more functionality out of JavaScript.

What are decorators?

If you are a Python or Java developer, you may already be familiar with the term decorator. But the question of whether to include decorators as a core language feature in JavaScript has been hotly debated over the years. It is no easy task to create such a feature without impacting the performance of the language interpretation because doing so can directly affect the way you manipulate a function.

import { @logged } from "./logged.mjs";

class C {
  @logged
  method(arg) {
    this.#x = arg;
  }

  @logged
  set #x(value) { }
}

new C().method(1);
// starting method with arguments 1
// starting set #x with arguments 1
// ending set #x
// ending method

For example, the @logged decorator shown above actually wraps functions and prints a log when a given function is called. This might seem like an easy thing to achieve, but wrapping a function with many decorators can lead to stack overflow because it happens in recursion with too many function calls.

That’s why decorators are one of the longest-standing feature requests in the ES community. In fact, this is the second iteration of the feature. It struggled to gain widespread adoption the first time around due to memory consumption issues for large applications. This iteration is optimized, but it seems we’ll need to wait to get it natively.

Writing a custom decorator

A decorator is a simple function that receives an argument function to be called. This means the interpretation should wrap the original function with the decorator function itself and keep it in memory with the original function name.

Let’s dig into the code to paint a clearer picture.

// logged.mjs

export decorator @logged {
  @wrap(f => {
    const name = f.name;
    function wrapped(...args) {
      console.log(`starting ${name} with arguments ${args.join(", ")}`);
      f.call(this, ...args);
      console.log(`ending ${name}`);
    }
    Object.defineProperty(wrapped, "name", {
      value: name,
      configurable: true
    });
    return wrapped;
  })
}

As you can see, there is another decorator, @wrap, that actually performs the function wrapping trick. This is one of a handful of built-in decorators that are available to use anywhere:

  • @wrap — Replace a method or the entire class with the return value of a given function
  • @register — Call a callback after the class is created
  • @expose — Call a callback with functions to access private fields or methods after the class is created
  • @initialize — Run a callback when creating an instance of the class

In this particular case, we have a @logged decorator that is wrapped to a passed function using the native @wrap decorator.

Problems with JIT and decorators

JavaScript’s JIT can optimize most coding use cases, but it only runs base optimizations during the warm-up process, where it initializes the global environment, functions, etc. The actual decorators run later and wrap/change an already-optimized function, which leads to nonoptimized code. Later, when that decorator is called, JIT will run again for that specific case and optimize it. This means the more decorators we have, the more times JIT will run during the “fast” phase of code execution. It’s no surprise that decorators are considered very resource-heavy.

Per the stage two proposal, developers would not rely on JIT optimizations. Instead, they would try to optimize things by making a few predefined decorators and building others based on those. This should help resolve some of the memory allocation issues because the standard, optimized decorators will cover the most resource-heavy operations.



Code static analysis

Analyzing JavaScript code is difficult because there are no static types that functions are returning and VSCode or Webstorm cannot always guess what function is going to return. With decorators, this task is even harder because, by design, a decorator like @register will change the way the function — and, therefore, the return type — works. This means the ES community is responsible for putting together not just an optimized implementation of decorators, but also all other supportive libraries and IDEs.

Is it time to adopt decorators?

It’s a bit early to use decorators in production, but many companies already use TypeScript/Babel decorators. Of course, you’ll see some Linting errors telling you that a decorator will consume a lot of memory, but you can still use them. The ECMAScript community has no definitive plans to roll it out in production, but for JavaScript coders, this is a second chance to have a fully functional programming cycle with JavaScript.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Tigran Bayburtsyan Cofounder and CTO at Hexact, Inc. Software scalability expert!

Leave a Reply