javascriptnode.jsecmascript-6co

How to access super class method inside of internal generator function?


Look at this classes, Base and Derived, it's just simple classes that has "name" as property:

class Base {
    constructor(name) {
        this.name = name;
    }
    printName() {
        console.log("Base: " + this.name);
    }
}

class Derieved extends Base {
    constructor(name) {
        super(name);
    }
    // Override
    printName() {
        // IIFE.
        (function() {
            super.printName();  // Can't use super here
        })();

        console.log("Derived: " + this.name);
    }
}

var obj = new Derieved('John');
obj.printName();

I want to invoke Base::printName from Derieved::printName. But some reason, I have to invoke inside of internal function of Derieved::printName.

But run above code, it fails with:

SyntaxError: 'super' keyword unexpected here

If I saved the reference of parent's method to variable, looks like it can called but can't access any properties, it saids undefined.

TypeError: Cannot read property 'name' of undefined

I just wrote that internal function is just normal function, but actually it's generator function, so I can't use arrow function:

get(match: T, options: IQueryOptions|void): Promise<Array<Object>|Error> {
    const superGet = super.get;

    return new Promise((resolve, reject) => {
        co(function*() {
            try {
                    // I need to invoke parent's get method here!!
                const accounts = yield superGet(match, options);

                ... // do something with accounts

                resolve(accounts);
            }
            catch(err) {
                ...
            }
        });
    });
}

Is there a way to to this? Why I can't save the reference of parent's method into variable?


Solution

  • super can be accessed only from child method, not from the scope of functions that are called inside this method.

    Generator functions are still functions and support binding. This will result in bound function that cannot be identified as generator function by its signature, but as long as coroutine library supports generic iterators (co does), that's fine.

    So basically it is

    get(...) {
        const superGet = super.get;
    
        return new Promise((resolve, reject) => {
            co(function*() {
                  ...
                  const accounts = yield superGet.call(this, match, options);
                  ...
            }.bind(this));
        });
    }
    

    Or even better:

    get(...) {
        const superGet = super.get.bind(this);
    
        return new Promise((resolve, reject) => {
            co(function*() {
                  ...
                  const accounts = yield superGet(match, options);
                  ...
            });
        });
    }
    

    There are several things that can be improved here. The first one is that it uses promise constructor antipattern. co already returns a promise, there's no need to wrap it with new Promise.

    Another thing is that it is beneficial to separate generator method and promisified method, for the sake of seamless inheritance. co supports delegated yields, it makes this even easier:

    class Base {
      get(...args) {
        return co(this._get.bind(this, ...args));
      }
    
      * _get(...) { ... }
    }
    
    class Child extends Base {
      * _get(...) {
        ...
        const accounts = yield* super._get(match, options);
        ...
      }
    }
    

    Both TypeScript and Babel support ES2017 async..await and are are able to fall back to co-like generator coroutines in ES6 target output. This makes co efficiently useless in transpiled JS projects, the code above becomes:

    class Child extends Base {
      async get(...) {
        ...
        const accounts = await super.get(match, options);
        ...
      }
    }