Typescript — функции обратного вызова становятся неопределенными при использовании из суперкласса

#node.js #typescript #inheritance #callback

Вопрос:

Я довольно новичок в машинописи и node.js но я хотел чему-то научиться и попробовать что-то новое с angular, node и express. Я все еще пытаюсь найти несколько хороших практик, чтобы разделить проект express на пару более мелких частей, и каким-то образом я столкнулся с проблемой. Я хотел создать абстрактный класс контроллера и некоторые подклассы, которые были бы типичными контроллерами REST, поддерживаемыми express.Router() .

Вот базовый класс — контроллер:

 import express, {Router} from "express";

export abstract class Controller {
  public router = express.Router();
  public path: string;

  protected constructor(path: string) {
    this.path = path;
    console.log('Calling initializeRoutes() from superclass');
    this.initializeRoutes();
  }
  abstract initializeRoutes(): void;
}

 

И подкласс — это всего лишь пример — класс UserController:

 import {Controller} from "./Controller";
import express from "express";

export class UserController extends Controller {

  private users = [
    {name: 'User1', password: 'password1'},
    {name: 'User2', password: 'password2'},
    {name: 'User3', password: 'password3'}
  ]

  constructor(path: string) {
    super(path);
    console.log('Calling initializeRoutes() from subclass');
    this.initializeRoutes();
  }

  initializeRoutes(): void {
    // console.log(this.router);
    console.log(this.getAllUsers);
    console.log(this.createAPost);
    // this.router.get(this.path, this.getAllUsers);
    // this.router.post(this.path, this.createAPost);
  }

  getAllUsers = (request: express.Request, response: express.Response) => {
    response.send(this.users);
  }

  createAPost = (request: express.Request, response: express.Response) => {
    const user = request.body;
    this.users.push(user);
    response.send(user);
  }
 

Основная проблема в том, что я хотел бы позвонить initializeRoutes() в базовый класс, но когда я это делаю, экспресс выдает ошибку, из-за которой я не могу использовать неопределенные обратные вызовы в маршрутизаторе. Я провел некоторую отладку и обнаружил, что при использовании initializeRoutes() в базовом классе обратные вызовы из подклассов не определены, в то время как при запуске из подклассов все в порядке:

 [1] [nodemon] starting `ts-node serverserver.ts`
[1] Calling initializeRoutes() from superclass
[1] undefined
[1] undefined
[1] Calling initializeRoutes() from subclass
[1] [Function (anonymous)]
[1] [Function (anonymous)]

 

Это почему? Я делаю что-то не так или что-то упускаю?
Заранее спасибо

ИЗМЕНИТЬ: Я попробовал предлагаемое решение:

 export class UserController extends Controller {

  constructor(path: string) {
    super(path);
    this.getAllUsers = this.getAllUsers.bind(this);
    this.createAPost = this.createAPost.bind(this);
    console.log('Calling initializeRoutes() from subclass');
    // this.initializeRoutes();
  }

  initializeRoutes(): void {
    // console.log(this.router);
    this.getAllUsers = this.getAllUsers.bind(this);
    this.createAPost = this.createAPost.bind(this);
    console.log(this.getAllUsers);
    console.log(this.createAPost);
    this.router.get(this.path, this.getAllUsers);
    this.router.post(this.path, this.createAPost);
  }

}
 

Когда я вставляю его в initializeRoutes() , я получаю:
[1] TypeError: Cannot read property 'bind' of undefined [1] at UserController.initializeRoutes (C:BlazejProjectspasteurservercontrollerUserController.ts:19:41)

Когда я помещаю его в конструктор, я получаю: [1] Error: Route.get() requires a callback function but got a [object Undefined]

Ответ №1:

 getAllUsers = (request: express.Request, response: express.Response) => {
  response.send(this.users);
}
 

Этот синтаксис представляет собой «поле класса», которое является функцией, которая находится в процессе добавления в javascript/typescript, но еще не совсем там. Вы можете ознакомиться с предложением здесь

Но даже несмотря на то, что он все еще проходит через процесс предложения, вы можете использовать (и используете) его сегодня, благодаря транспиляторам. Typescript возьмет написанный вами код и превратит его в ближайший эквивалентный код. Например, это:

 class Example {
  constructor() {
    super();
  }

  someFunction = () => {};
}
 

Превращается в основном в это:

 class Example {
  constructor() {
    super();
    this.someFunction = () => {};
  }
}
 

Но обратите внимание, что транспилированный код помещает создание функции после вызова super(). Так и должно быть, но это означает, что, пока super работает, функция еще не существует.

Поэтому, чтобы функции существовали раньше времени, вам нужно будет использовать обычные методы класса вместо полей класса, как в:

 getAllUsers(request: express.Request, response: express.Response) {
  response.send(this.users);
}

createAPost(request: express.Request, response: express.Response) {
  const user = request.body;
  this.users.push(user);
  response.send(user);
}
 

Но, конечно, вы, вероятно, сделали их как функции со стрелками специально, потому что вам нужно значение this для правильной работы. Поэтому для этого я бы рекомендовал привязать функции. Если вам не нужны связанные функции во время инициализации, вы можете сделать это в конструкторе вашего дочернего класса:

   constructor(path: string) {
    super(path);
    this.getAllUsers = this.getAllUsers.bind(this);
    this.createAPost = this.createAPost.bind(this);
  }
 

Или, если они вам понадобятся во время строительства, возможно, сделайте это в initializeRoutes

   initializeRoutes(): void {
    this.getAllUsers = this.getAllUsers.bind(this);
    this.createAPost = this.createAPost.bind(this);
    this.router.get(this.path, this.getAllUsers);
    this.router.post(this.path, this.createAPost);
  }
 

Комментарии:

1. Спасибо за очень быстрый ответ. Я понятия не имел, что это так работает. Во всяком случае, все еще не работает. Я попробовал оба решения — привязку обратных вызовов в конструкторе и инициализацию маршрутов (), и все равно получил: ` Ошибка: Route.get() требует функции обратного вызова, но получил [объект не определен] «