A way to fire logic at the end of completion of the current class method (regardless of super call order).

# #!/JoePea (8 months ago)

I many times find myself in cases where a base class wants to ensure that logic is always fired after the current method's execution, so that for example no matter in which order sub classes call the super method, the super method can still guarantee that logic fires after the whole stack of the same method in the class hierarchy.

So what I can do now is use Promise.resolve().then(() => { ... }) to

schedule that logic for later, that way all the invocations of a foo method along the class hierarchy have all fired. But this means that other code can also fire before the next microtask.

Is there some way to do it? If not, I wonder if some language feature for doing it would be possible?

# Isiah Meadows (8 months ago)

I've also had several scenarios where I could've used this personally. I feel ES classes are overly restrictive in preventing this, since it basically forces you to force subclasses to do something like this.init() right after the class is allocated, leaking implementation details left and right.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# kai zhu (8 months ago)

I feel ES classes are overly restrictive in preventing this, since it basically forces you to force subclasses to do something like this.init() right after the class is allocated, leaking implementation details left and right.

its not a design-flaw. the flaw is you trying to shoehorn “classical” inheritance-based design-patterns on a language better suited to use static-functions to baton-pass json-data (over class-instance).

here’s a real-world example of a dbTableCreateOne static-function to initialize a mongo-like collection/table, and then sync it with indexeddb persistence-store [1]. imagine how much more difficult these [common-case] async-initializations would be using inheritance-based constructors.

local.dbTableCreateOne = function (options, onError) {
/*
 * this function will create a dbTable with the given options
 */
    var self;
    options = local.objectSetOverride(options);
    // register dbTable
    self = local.dbTableDict[options.name] =
        local.dbTableDict[options.name] || new local._DbTable(options);
    ...
    // restore dbTable from persistent-storage
    self.isLoaded = self.isLoaded || options.isLoaded;
    if (!self.isLoaded) {
        local.storageGetItem('dbTable.' + self.name + '.json', function (error, data) {
            // validate no error occurred
            local.assert(!error, error);
            if (!self.isLoaded) {
                local.dbImport(data);
            }
            self.isLoaded = true;
            local.setTimeoutOnError(onError, 0, null, self);
        });
        return self;
    }
    return local.setTimeoutOnError(onError, 0, null, self);
};

p.s. - going off-topic, but above-mentioned code/library (as well as proposals for standard tree/stl libraries) shouldn’t even exist if javascript had a builtin/standard sqlite3 library (like python).

[1] initialize a db-table and sync it with persistence-store kaizhu256/node-db-lite/blob/2018.4.23/lib.db.js#L1861, kaizhu256/node-db-lite/blob/2018.4.23/lib.db.js#L1861

[2] discuss including tree and stl in proposed standard-library tc39/proposal-javascript-standard-library#16, tc39/proposal-javascript-standard-library#16

tc39/proposal-javascript-standard-library#16, tc39/proposal-javascript-standard-library#16

# Jordan Harband (8 months ago)

If the superclass constructor has a way to run any code after subclass constructors, then implementation details of the subclasses are then leaked.

# Isiah Meadows (8 months ago)

I get that, but it's usually part of the API subclassing contract that the superclass explicitly depends on certain parts of the subclass. Abstract classes immediately come to mind, and I'd say it's no more leaking than any inherited method. It's not giving them any more access to information than they would've gotten from an explicit if (new.target === Subclass) this.init() call at the end (which is basically what I want mod the exposed method).


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Isiah Meadows (8 months ago)

I presume you've never written non-trivial server-side logic in Node. I find myself using classes far more often in Node than in the browser simply because it's more than just passing strings and object bags around. You've got caching, process management, and even sometimes task queues. The traditional MVC architecture doesn't need classes to implement in Node (and the corresponding idioms don't use them, either), but it's all the machinery around it that result in all the server-side classes.

Also, I'd like to note a few things:

  • Some functional languages, notably OCaml, support classes with classical inheritance. Inheritance does actually help from time to time.
  • JS is not opinionated on the matter - it supports both styles. In fact, it lacks some of the utilities for pure POJO passing and manipulation, while classes are mostly complete. In reality, it's slightly biased in favor of classes, something several people have been complaining about (me included).
  • I'm very well used to passing POJOs around to static functions - it's called functional programming.
# Michael Haufe (8 months ago)

You can use a Proxy in the base class:

<script>

let handlerExample = { get(target, prop) { let feature = target[prop] return feature instanceof Function ? function () { console.log('Before call'); let result = feature.apply(this, arguments) console.log('After call'); return result } : feature } }

class Base { constructor() { return new Proxy(this, handlerExample) } m1() { return "m1" } }

class Sub extends Base { m1() { return override ${super.m1()} } m2() { return m2 } }

let base = new Base() console.log(base.m1())

let sub = new Sub() console.log(sub.m1()) console.log(sub.m2()) </script>

/Michael

On Fri, Feb 8, 2019 at 1:22 AM #!/JoePea <joe at trusktr.io> wrote:

I many times find myself in cases where a base class wants to ensure that logic is always fired after the current method's execution, so that for example no matter in which order sub classes call the super method, the super method can still guarantee that logic fires after the whole stack of the same method in the class hierarchy.

# Michael Haufe (3 months ago)

Revisiting this topic: why is the Template Method pattern not acceptable to accomplish this?

<script>

class Base { constructor() { this._beforeAction() this._action() this._afterAction() } _beforeAction(){ console.log(Base._beforeAction()) } _action(){ console.log(Base._action()) } _afterAction(){ console.log(Base._afterAction()) } }

class A extends Base { _action(){ console.log(A._action()) } }

let a = new A() // console reads: // > Base._beforeAction() // > A._action() // > Base._afterAction() </script>

/Michael

Monday, February 11, 2019 10:34 PM Michael Haufe <tno at thenewobjective.com> wrote:

You can use a Proxy in the base class:

<script> let handlerExample = { get(target, prop) { let feature = target[prop] return feature instanceof Function ? function () { console.log('Before call'); let result = feature.apply(this, arguments) console.log('After call'); return result } : feature } }

class Base { constructor() { return new Proxy(this, handlerExample) } m1() { return "m1" } }

class Sub extends Base { m1() { return override ${super.m1()} } m2() { return m2 } }

let base = new Base() console.log(base.m1())

let sub = new Sub() console.log(sub.m1()) console.log(sub.m2()) </script>

/Michael

On Fri, Feb 8, 2019 at 1:22 AM #!/JoePea <joe at trusktr.io> wrote:

I many times find myself in cases where a base class wants to ensure that logic is always fired after the current method's execution, so that for example no matter in which order sub classes call the super method, the super method can still guarantee that logic fires after the whole stack of the same method in the class hierarchy.

# Naveen Chawla (3 months ago)

Can anyone tell me what's wrong with a really simple approach? Wherever you are calling method(), just call methodWithAfter():

//in base class methodWithAfter(){ method(); after() }

What am I missing, guys?

# Michael Haufe (3 months ago)

Assuming your base class:

<script>

class Base { methodWIthAfter(){ method() after() } } </script>

If I override the method, there is no guarantee that I called super.methodWithAfter(), so after() is never executed.

<script>

class A extends Base { methodWithAfter() { method2() } } </script>

Additionally, requiring me to call super.methodWithAfter() is an Anti-Pattern: en.wikipedia.org/wiki/Call_super

/Michael

From: Naveen Chawla <naveen.chwl at gmail.com>

Sent: Friday, July 19, 2019 3:24 AM To: Michael Haufe <tno at thenewobjective.com>

Cc: joe at trusktr.io; es-discuss <es-discuss at mozilla.org>

Subject: Re: A way to fire logic at the end of completion of the current class method (regardless of super call order).

Can anyone tell me what's wrong with a really simple approach? Wherever you are calling method(), just call methodWithAfter():

//in base class methodWithAfter(){ method(); after() }

What am I missing, guys?

On Fri, 19 Jul 2019 at 07:43, Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>> wrote:

Revisiting this topic: why is the Template Method pattern not acceptable to accomplish this?

<script>

class Base { constructor() { this._beforeAction() this._action() this._afterAction() } _beforeAction(){ console.log(Base._beforeAction()) } _action(){ console.log(Base._action()) } _afterAction(){ console.log(Base._afterAction()) } }

class A extends Base { _action(){ console.log(A._action()) } }

let a = new A() // console reads: // > Base._beforeAction() // > A._action() // > Base._afterAction() </script>

/Michael

Monday, February 11, 2019 10:34 PM Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>> wrote:

You can use a Proxy in the base class:

<script> let handlerExample = { get(target, prop) { let feature = target[prop] return feature instanceof Function ? function () { console.log('Before call'); let result = feature.apply(this, arguments) console.log('After call'); return result } : feature } }

class Base { constructor() { return new Proxy(this, handlerExample) } m1() { return "m1" } }

class Sub extends Base { m1() { return override ${super.m1()} } m2() { return m2 } }

let base = new Base() console.log(base.m1())

let sub = new Sub() console.log(sub.m1()) console.log(sub.m2()) </script>

/Michael

On Fri, Feb 8, 2019 at 1:22 AM #!/JoePea <joe at trusktr.io<mailto:joe at trusktr.io>> wrote:

I many times find myself in cases where a base class wants to ensure that logic is always fired after the current method's execution, so that for example no matter in which order sub classes call the super method, the super method can still guarantee that logic fires after the whole stack of the same method in the class hierarchy.

# Naveen Chawla (3 months ago)

Is this a problem in the real world? Most people understand that simply overriding methods can break functionality. Therefore, doesn't the simple approach I gave suffice for the requirement in at least most cases? Are you able to describe a scenario in which we really need to protect people who are overriding a method from breaking functionality that they might not even expect to occur after they've overridden it?

# Michael Haufe (3 months ago)

You should read the original message I quoted and the related thread:

esdiscuss/2019-February/thread.html#52279

Assuming what people understand is a recipe for disappointment in my experience. Also, we should differentiate between “simple” and “simplistic”.

/Michael

From: Naveen Chawla <naveen.chwl at gmail.com>

Sent: Friday, July 19, 2019 3:51 AM To: Michael Haufe <tno at thenewobjective.com>

Cc: joe at trusktr.io; es-discuss <es-discuss at mozilla.org>

Subject: Re: A way to fire logic at the end of completion of the current class method (regardless of super call order).

Is this a problem in the real world? Most people understand that simply overriding methods can break functionality. Therefore, doesn't the simple approach I gave suffice for the requirement in at least most cases? Are you able to describe a scenario in which we really need to protect people who are overriding a method from breaking functionality that they might not even expect to occur after they've overridden it?

On Fri, 19 Jul 2019 at 09:32, Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>> wrote:

Assuming your base class:

<script>

class Base { methodWIthAfter(){ method() after() } } </script>

If I override the method, there is no guarantee that I called super.methodWithAfter(), so after() is never executed.

<script>

class A extends Base { methodWithAfter() { method2() } } </script>

Additionally, requiring me to call super.methodWithAfter() is an Anti-Pattern: en.wikipedia.org/wiki/Call_super

/Michael

From: Naveen Chawla <naveen.chwl at gmail.com<mailto:naveen.chwl at gmail.com>>

Sent: Friday, July 19, 2019 3:24 AM To: Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>>

Cc: joe at trusktr.io<mailto:joe at trusktr.io>; es-discuss <es-discuss at mozilla.org<mailto:es-discuss at mozilla.org>>

Subject: Re: A way to fire logic at the end of completion of the current class method (regardless of super call order).

Can anyone tell me what's wrong with a really simple approach? Wherever you are calling method(), just call methodWithAfter():

//in base class methodWithAfter(){ method(); after() }

What am I missing, guys?

On Fri, 19 Jul 2019 at 07:43, Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>> wrote:

Revisiting this topic: why is the Template Method pattern not acceptable to accomplish this?

<script>

class Base { constructor() { this._beforeAction() this._action() this._afterAction() } _beforeAction(){ console.log(Base._beforeAction()) } _action(){ console.log(Base._action()) } _afterAction(){ console.log(Base._afterAction()) } }

class A extends Base { _action(){ console.log(A._action()) } }

let a = new A() // console reads: // > Base._beforeAction() // > A._action() // > Base._afterAction() </script>

/Michael

Monday, February 11, 2019 10:34 PM Michael Haufe <tno at thenewobjective.com<mailto:tno at thenewobjective.com>> wrote:

You can use a Proxy in the base class:

<script> let handlerExample = { get(target, prop) { let feature = target[prop] return feature instanceof Function ? function () { console.log('Before call'); let result = feature.apply(this, arguments) console.log('After call'); return result } : feature } }

class Base { constructor() { return new Proxy(this, handlerExample) } m1() { return "m1" } }

class Sub extends Base { m1() { return override ${super.m1()} } m2() { return m2 } }

let base = new Base() console.log(base.m1())

let sub = new Sub() console.log(sub.m1()) console.log(sub.m2()) </script>

/Michael

On Fri, Feb 8, 2019 at 1:22 AM #!/JoePea <joe at trusktr.io<mailto:joe at trusktr.io>> wrote:

I many times find myself in cases where a base class wants to ensure that logic is always fired after the current method's execution, so that for example no matter in which order sub classes call the super method, the super method can still guarantee that logic fires after the whole stack of the same method in the class hierarchy.