Search code examples
javascriptnode.jstypescriptexpresstsconfig

Unable to use super to call inherited class function


I am trying to call the function of the parent class from the child but the keyword super is throwing and error. I am using typescript, here is a snippet of the package.json from the project.

{
  "scripts": {
    "build": "tsc",
    "start": "nodemon",
    "prod": "npm run build && npm run start"
  },
  "dependencies": {
    "body-parser": "^1.18.3",
    "dotenv": "^6.1.0",
    "express": "^4.16.4"
  },
  "devDependencies": {
    "@types/body-parser": "^1.17.0",
    "@types/dotenv": "^4.0.3",
    "@types/express": "^4.16.0",
    "@types/node": "^10.12.2",
    "nodemon": "^1.18.5",
    "ts-node": "^7.0.1",
    "tslint": "^5.11.0",
    "typescript": "^3.1.6"
  }
}

The parent class

export default class baseController {

  public response = (message = "", status = 200) => (
    req: Request,
    res: Response
  ) => {
    return res.status(status).send({
      status: true, // true if success, false if faliure
      message: message, // message to display incase of error
      payload: []
    });
  };
}

The child class

import BaseController from "../baseController";

export default class UsersController extends BaseController {

  constructor() {
    super();
  }

  public fetchUsers = async () => {
    return super.response("testing");
  };
}

The code crashes on the line return super.response("testing"); with the error super keyword unexpected here.

Here is my tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "noImplicitAny": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "./dist",
    "pretty": true,
    "baseUrl": "./src",
    "alwaysStrict": true,
    "paths": {
      "*": ["node_modules/*", "src/types/*"]
    }
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

Solution

  • This case is one of several reasons why prototype methods may be preferred over arrow class fields (instance methods), as explained in this answer.

    There are several problems here.

    One problem is that there's no super.response. super refers to parent class prototype, while response is instance method.

    Another problem is that the target is ES6. async is transpiled to generators, and super is not transpiled, this results in incorrect code:

    fetchUsers.a = () => __awaiter(this, void 0, void 0, function* () { return super.response("testing") });
    

    Only arrow functions can get super from parent scope, super is not allowed inside non-arrow functions. Since there are no arrow generators, super use is invalid inside a generator. While TypeScript is able to handle super properly in async prototype methods:

    fetchUsers() {
        const _super = name => super[name];
        return __awaiter(this, void 0, void 0, function* () { _super.response("testing").call(this); });
    }
    

    Yet another problem is that referring to super in a class that doesn't override response is semantic mistake. Child class already inherits response. It can be used as this method.

    It should be:

    export default class baseController {
      public response(message = "", status = 200) (...) { ... }
    }
    
    export default class UsersController extends BaseController {
      public async fetchUsers() {
        return this.response("testing");
      };
    }
    

    If fetchUsers is expected to be used as a callback (this is the only good use for arrow methods), it should be bound to this context in constructor:

      public fetchUsers = this.fetchUsers.bind(this);
    
      public async fetchUsers() {
        return this.response("testing");
      };
    

    Where fetchUsers class field is sugar syntax for constructor body.