tc39/proposal-decorators

It isn't clear when `addInitializer` functions are called on class decorator

Closed this issue · 2 comments

Hello and sorry if this is not a proper way to discuss this. I am new to the concept of modern ECMA decorators and I was playing with them in TypeScript. I wanted to run some code each time a class is instantiated. I have figured I could decorate my class and add my callback to addInitializer of the context, kind of like this:

function WithInitializer() {
  return (_: unknown, context: ClassDecoratorContext) => {
    context.addInitializer(() => {
      console.log('Init');
    });
  };
}

@WithInitializer()
class Test {
  constructor() {
    console.log('Test');
  }
}

console.log('Pre');
const test = new Test();
console.log('Post');

With this configuration I would expect the following output:

Pre
Init
Test
Post

However Init is printed before everything.

Am I missing something or is this an issue with TypeScript implementation? When are those functions supposed to execute? If they execute not when the class is instantiated, what is the purpose of them, why not just run your code immediately?

Here's a playground link

Initializers for class definitions initialize the class definition itself. Think of them like static fields or blocks, they run once on the class itself to fully initialize it.

To understand why this is necessary, vs just returning a new class definition, you can look at some of the older issues discussing this. A good example is defining web components based on a class. The component must be registered after static fields are assigned, due to the way the API works. However, static fields must be assigned after decorators have returned the new class definition, so they can use the finalized class definition in their initializers. So a step was needed after fields are assigned, thus the timing.

to run initialization code on class instantiation, extend the class and put the code in the constructor instead.

Got it, thanks for explanation.