Decorators and Forwarding

Hello,

I’m currently learning about Decorators and Forwarding in Javascript. The basic idea is easy enough to understand, but it got confusing once I started working through the examples.

I struggled with the tasks at the bottom of the page. Why do we need to use .apply() to set this when forwarding functions that aren’t dealing with an object and don’t have a this?

The last task [Throttle Decorator] was pretty tricky. I understand most of it, but am still confused about certain things they did, like saving and passing the this context for a function that isn’t dealing with an object, or how the arguments parameter in the forwarded function equals the parameters passed to the decorated function.

This stuff is harder for me than anything I have encountered in Javascript so far, including the Intermediate Algorithms and the Javascript Projects.

Is this all stuff that will be needed on the job? It seems pretty important. Also, how much more difficult does it get? Like with React, Backend, and DB stuff? If it gets much harder than this, I’m not sure I will be able to keep up.

Thank you for reading.

It is showing you how to implement decorators. It is complex because the technique is abstract. Decorators are an application of metaprogramming.

Yes and no. tl/dr it is not unlikely you would use decorators, but you’d likely use the syntax provided for them by language tools. And you may write some but you’d likely not have to write them in the way described in the article.

EDIT and herein lies the rub: as an application of metaprogramming, they are an advanced feature. They are not beginner friendly. Metaprogramming allows you to add features to a language that were not there to start off with. What it can produce is features that may be ergonomic to use. But creation of the mechanism by which you get those features is going to be tricky.

So, at a basic level:

  • You have a function.
  • You want to wrap some extra functionality around that function.
  • The ideal situation is that the functionality is generic. That it is something you might want to apply to any function in your code. The name of the functionality often provides a clue (“throttle”, “memoize”, “debounce”).

So, say I want to observe {things} happening in an application. And that the way I do that is to send information to some remote service. So say I function that takes some data about the {thing} and sends a log message to the service.

I could add that function everywhere I want to notify the service about a {thing}, like


async function logIn(uname, pword) {
  try {
    const logInResp = await validateOnServer(uname, pword);
    logToMyService("login success", { payload: { user: uname, resp: logInResp }});
    return setAppToLoggedInState(logInResp);
  } catch (loginError) {
    logToMyService("login failure", { payload: { user: uname, err: loginError }});

...some more code

For a small application, that is reasonable. But it is manual work, error prone.

That monitoring doesn’t have anything to do with the log-in code. It is something completely separate.

So there could be a “withCloudMonitoring” function. That would wrap any function of interest. This is more ergonomic when you use the technique, you would do:

await withCloudMonitoring(logIn(uname, pword))

But to write the “withCloudMonitoring” function, that needs a lot of work. For it to be useful it has to work in any situation. You aren’t calling a function and passing data in. You would be writing a function that needs to peer inside some running code. You don’t know what that code will be in advance.

So there lies much of the complexity.

But the first example (wrap a basic function in a function; no this, call, apply &c). Yes, that is what decorators are. But doing that in JS is not called “using decorators”. It’s just “using functions”.

As an example: React provides a function called memo. That would fit the description of decorators in the article. But it isn’t something, in JavaScript, that you would tend to refer to as a decorator.

Decorators as a specific named thing tends to be an OO programming concept. A pattern. And when I say

Yes and no.

You may use decorators. But it is also likely that you will not need to implement the techniques described in your article. At least in any detail.

In a class-based OO language (like Java, or Ruby, or Python). Decorators are a way of avoiding subclassing. Subclassing is the creation of a new class with same properties as the old class plus some changes.

So you have, say, a Python app. And you have access to my “withCloudMonitoring” function, provided by some monitoring library. And you add it:

class UserAccount:
    # ...some code
    @with_cloud_monitoring 
    def login(self, uname, pword):
      # Do stuff here

# ...more code

Notice that @ syntax before the function head. That’s syntactic sugar for wrapping a function in a decorator. Does what the examples in your article do though. It says “run with_cloud_monitoring(login)”.

JavaScript is an OO language. The way JS’ OO works is different to most other OO languages. But you can apply the same techniques/patterns, with success.

TypeScript, which is common IRL, provides very similar syntactic sugar for decorators. JavaScript is likely to get the same soon . This takes away most of the boilerplate code shown in your article (and provides extra niceties). Some common libraries/frameworks use decorators (Angular, Lit, NestJS, TypeORM for example).

Some examples (you don’t need to understand what they’re doing, just as examples of the syntax). A component in Angular:

@[Component](https://angular.io/api/core/Component)({ 
  selector: 'app-product-alerts', 
  templateUrl: './product-alerts.component.html', 
  styleUrls: ['./product-alerts.component.css'] 
}) 

export class ProductAlertsComponent { }

A web component in Lit

@customElement("my-timer")
export class MyTimer extends LitElement {
  @property() duration = 60;
  @state() private end: number | null = null;
  @state() private remaining = 0;

  render() {
    const {remaining, running} = this;
    const min = Math.floor(remaining / 60000);
    const sec = pad(min, Math.floor(remaining / 1000 % 60));
    const hun = pad(true, Math.floor(remaining % 1000 / 10));
    return html`
      ${min ? `${min}:${sec}` : `${sec}.${hun}`}
      <footer>
        ${remaining === 0 ? '' : running ?
          html`<span @click=${this.pause}>${pause}</span>` :
          html`<span @click=${this.start}>${play}</span>`}
        <span @click=${this.reset}>${replay}</span>
      </footer>
    `;
  }

  //...snip

A controller in NestJS:

import { Controller, Get } from '@nestjs/common'; 

@Controller('cats') 
export class CatsController { 
  @Get() 
  findAll(): string { 
    return 'This action returns all cats'; 
  } 
} 

And an entity in TypeORM:

@Entity("users")
export class User {
    @Column({ primary: true })
    id: number

    @Column({ type: "varchar", length: 200, unique: true })
    firstName: string

    @Column({ nullable: true })
    lastName: string

    @Column({ default: false })
    isActive: boolean
}

Note these are just pulled straight from the documentation.

1 Like

Thank you for that detailed explanation.