Class decorators and async process

# Gray Zhang (10 years ago)

I’m wondering if there is any way to combine a class decorator and an async process together? A common case would be a generic IoC implementation:

Since in JavaScript IoC we load runtime modules async by a config file, the interface may be:

{Promise} ioc.getComponent({string} componentName)

which resolves the returned Promise giving required instance of componentName, and this is a method that cannot be sync since we need a config file to map componentName to its implementing module and load the module lazily (for performance reason)

This is an async process so if we add a decorator to a class property:

function inject(name) {
    return (target, key, descriptor) {
        // Note this returns a promise, not the actual property value
        descriptor.initializer = () => ioc.getComponent(name);
    }
}

class Hero {
    @inject('knife')
    weapon = null
      
    hit(enemy) {
        enemy.heath -= (this.weapon.power - enemy.defense);
    }
}

This code may not work, but nobody likes there injected properties to be all async getters and all code logics become unnecessarily complex by introducing so many async processes

How so we think of such common case, should I just make the ioc.getComponent sync without considerations to performance, or should I give up the decorator solution?

Thanks

Best

Gray Zhang

# Brendan Eich (10 years ago)

Gray Zhang wrote:

Hi all:

I’m wondering if there is any way to combine a class decorator and an async process together? A common case would be a generic IoC implementation:

First, too many undefined terms and made-up syntax extensions just makes for confusion. Can you define "async process"? To optimize I'll assume you mean async function, a function returning a promise.

Since in JavaScript IoC

IoC = Inversion of Control -- just checking!

we load runtime modules async by a config file, the interface may be:

|{Promise} ioc.getComponent({string} componentName) |

Nicer to use reserved type annotation syntax:

| ioc.getComponent(componentName: string): Promise|

I hope that's what {T} D means!

which resolves the returned Promise giving required instance of |componentName|, and this is a method that cannot be sync since we need a config file to map |componentName| to its implementing module and load the module lazily (for performance reason)

This is an async process so if we add a decorator to a class property:

|function inject(name) { return (target, key, descriptor) {|

Missing function after return?

| // Note this returns a promise, not the actual property value descriptor.initializer = () => ioc.getComponent(name); } }

class Hero { @inject('knife') weapon = null|

So the decorator calls the anonymous function with key='knife', target=instance-of-Hero, and descriptor the property descriptor, extended with .initializer?

BTW the idea of using property descriptors for decorators got push-back and an alternative suggestion at the March TC39 meeting.

|

 hit(enemy) {
     enemy.heath -= (this.weapon.power - enemy.defense);
 }

} |

This code may not work, but nobody likes there injected properties to be all async getters and all code logics become unnecessarily complex by introducing so many async processes

Have you read about await and async in ES7?

How so we think of such common case, should I just make the |ioc.getComponent| sync without considerations to performance,

"performance" is not really accurate: responsiveness and even deadlock avoidance come to mind. If you're talking about a browser API, then you can't do sync loading (apart from bad old sync XHR), so async/await is the way to go.

or should I give up the decorator solution?

Is the decorator idea based on extended property descriptors actually being used, e.g., with Babel? Sorry if I missed it.

# Gray Zhang (10 years ago)

Sorry for some confusing descriptions, IoC means Inversion of Control here, I’d like to put all code together here:

// A simple implement of IoC
ioc.getComponent = (name) {
    return new Promise((resolve) => {
        let iocConfig = parseConfig(name);
        let moduleId = iocConfig.module;
        require([moduleId], function (ModuleClass) {
            let instance = new ModuleClass();
            injectProperties(instance, iocConfig);
            resolve(instance);
        });
    });
}

let inject = (name) => {
    return (target, key, descriptor) => {
        // target = hero
        // key = 'weapon'
        // Use initializer to get the property value
        descriptor.initializer = () => {
            return ioc.getComponent(name);
        };
    };
};

class Hero {
    // We need a knife from ioc as this hero's weapon
    @inject('knife')
    weapon = null;
     
    hit(enemy) {
        // Problem here
        enemy.heath -= this.weapon.power - enemy.defense;
    }
}

The problem is, as I know, when a decorator implements the descriptor.initializer the property value should be the return value of this initializer, however the initializer should be sync which directly returns the value but not a promise or a async function, we can’t await for a descriptor.initializer

In this case, in my hit method, the this.weapon is a Promise but not a knife instance, Promise does not have e a power property so hit method fails, I may correct the code:

hit(enemy) {
    return this.weapon.then((weapon) => {
        emeny.heath -= weapon.power - enemy.defense;
    });
}

It’s OK, I just wait for this.weapon to resolve, but then my hit method becomes async, and everything based on @inject property should be async, which is not actually what I want.

Is the decorator idea based on extended property descriptors actually being used, e.g., with Babel? Sorry if I missed it. Decorator is now supported with Babel 5.0 and I was trying this these days, expecting it could be integrated with our IoC framework, just as how Spring works in Java.

Best

Gray Zhang

# Brendan Eich (10 years ago)

Gray Zhang wrote:

It’s OK, I just wait for |this.weapon| to resolve, but then my |hit| method becomes async, and everything based on |@inject| property should be async, which is not actually what I want.

Async can be contaminating, yes.

Sync is out of bounds in browsers, and for Node.js apart from require

# Alexandre Louis Marc Morgaut (10 years ago)

On 26 May 2015, at 01:09, Brendan Eich <brendan at mozilla.org> wrote:

Gray Zhang wrote:

It’s OK, I just wait for |this.weapon| to resolve, but then my |hit| method becomes async, and everything based on |@inject| property should be async, which is not actually what I want.

Async can be contaminating, yes.

That’s exactly a point that reached my interest in the given example

My brain looking at this contamination wondering things like:

  • Then we would need “await @inject(‘knife’)” ?
  • Then we would need “async class Hero {…}” ?
  • Then users should take care to do “await new Hero()” ?
  • and/or would it mean that we could do instead “class extends Promise { … }" ?

Ok… Are async Classes / Constructors crazy or just natural evolutions we could/should be prepared to see in the future !?

Just wondering, I’m curious, I’d understand either that to be crazy or natural ;-)

Sync is out of bounds in browsers, and for Node.js apart from require -- a rift indeed between the two embeddings.

Just to be fair, Sync is still appreciated in some situations in JS like

  • in Dedicated Workers,
  • in other SSJS platforms like RingoJS, Wakanda, APE, …
  • as it is also in Node.js when writing CLI applications (then tricks are occasionaly used in addition to Sync APIs) ;-)

,

Alexandre

# Brendan Eich (10 years ago)

Alexandre Louis Marc Morgaut wrote:

Sync is out of bounds in browsers, and for Node.js apart from require -- a rift indeed between the two embeddings.

Just to be fair, Sync is still appreciated in some situations in JS like

  • in Dedicated Workers,

Good point. Still not in specs but proposed via W3C or WHATWG (I forget). Anyone have a fresh link?

  • in other SSJS platforms like RingoJS, Wakanda, APE, …

Sure, but I wrote "in browsers" :-|. Didn't mean to exclude non-Node SSJS platforms. Then there are the "isomorphic" approaches that may compile out sync into async, e.g. Meteor.

  • as it is also in Node.js when writing CLI applications (then tricks are occasionaly used in addition to Sync APIs);-)

Right, thanks.