calling function inside javascript class : this.function is not a function

Issue

i’ve already seen this

How to access the correct `this` inside a callback

it doesn’t mention classes , at least this syntax im using

im new to nodejs and js class so bear with me , im writing a app with mvc like architecture

i got two methods in my controller , wallet and generateWallet

user will call wallet method via http request, it will check the database for wallet and if not exists it will call another function generateWallet which is supposed to be a private function to generate a new wallet and thats when i get the error

here i my code

class DashboardController {

  async wallet(req, res) {

    let wallet = await UserWalletRepository.find({
      user_id: req.authUser.id,
    });

    if (wallet) return res.send(wallet);

    let newWallet = await this.generateWallet(); 

    res.send(newWallet);
  }

  async generateWallet() {

    let generatedWallet = await WalletService.generateWallet();
    let newWallet = await UserWalletRepository.create({
      user_id: req.authUser.id,
      ...generatedWallet,
    });

    return newWallet;
  }
}

module.exports = new DashboardController();

here is the error

TypeError: this.generateWallet is not a function
at wallet (C:\node\first\app\controller\api\dashboard-controller.js:16:32)
at processTicksAndRejections (internal/process/task_queues.js:97:5)

specifically this like

let newWallet = await this.generateWallet(); 

i assume something is changing this keyword but i cant figure out what , there are routers and other codes involved in the stack but i dont think they have any effect since we are inside the function when we get this error ?

here is how i’ve set up my filder/files

so i have router folder with api and web folder inside , containing route files for each section for example in routes/api/dashboard.js i have

const DashboardController = require("../../app/controller/api/dashboard-controller");
const { AuthMiddleware } = require("../../app/middleware/auth");
module.exports = {
  group: {
    prefix: "/dashboard",
    middleware: [AuthMiddleware],
  },
  routes: [
    {
      method: "get",
      path: "/",
      handler: DashboardController.index,
    },
    {
      method: "get",
      path: "/wallet",
      handler: DashboardController.wallet,
    },
  ],
};

and i have routes/inedx.js which grabs all the route files and adds them to express

const express = require("express");
require("express-async-errors");
const webRoutes = require("./web/index");
const apiRoutes = require("./api/index");
class Router {
  constructor() {
    this.router = express.Router();
  }

  create(app) {
    this._attachMiddleware();
    this._attachWebRoutes();
    this._attachApiRoutes();
    app.use(this.router);
  }

  _attachMiddleware() {
    this.router.use(express.json());
  }


  _attachWebRoutes() {
    this._attachRoutes(webRoutes);
  }
  _attachApiRoutes() {
    this._attachRoutes(apiRoutes, "/api");
  }

  _attachRoutes(routeGroups, globalPrefix = "") {
    routeGroups.forEach(({ group, routes }) => {
      routes.forEach(({ method, path, middleware = [], handler }) => {
        this.router[method](
          globalPrefix + group.prefix + path,
          [...(group.middleware || []), ...middleware],
          handler
        );
      });
    });
  }


}

module.exports = new Router();

i have server/index.js

const express = require('express')
const Router = require('../router/index')

class Server {

    constructor(port){
        this.port = port ; 
        this.app = express();
    }


    start(){
        this._setupRoutes();
        this._listin();
    }

    _listin(){
        this.app.listen(this.port , ()=>{
            console.log('app running on port' , this.port);
        })
    }

    _setupRoutes(){
        
        Router.create(this.app)

    }
}

module.exports = Server ;

and finally my index.js

const Server = require('./server/index');
const app = new Server(8080);
app.start();

Solution

Problem you are having is related to how you assign the DashboardController.wallet to the handler which makes this to be unbound:

{
  method: "get",
  path: "/wallet",
  handler: DashboardController.wallet,
}

Since you don’t have class variables, you can make your methods static and not use this at all:

class DashboardController {
  static async wallet(req, res) {
    let wallet = await UserWalletRepository.find({
      user_id: req.authUser.id,
    });

    if (wallet) return res.send(wallet);

    let newWallet = await DashboardController.generateWallet();

    res.send(newWallet);
  }

  static async generateWallet() {
    let generatedWallet = await WalletService.generateWallet();
    let newWallet = await UserWalletRepository.create({
      user_id: req.authUser.id,
      ...generatedWallet,
    });

    return newWallet;
  }
}
module.exports = DashboardController;

alternatively, you can use .bind() to set the this explicitly:

{
  method: "get",
  path: "/wallet",
  handler: DashboardController.wallet.bind(DashboardController),
}

Answered By – Arootin Aghazaryan

Answer Checked By – Marie Seifert (AngularFixing Admin)

Leave a Reply

Your email address will not be published.