this is undefined outside from constructor - javascript

I created an Express REST API using Dependency Injection with Inversify. I have a basic controller class
import { Request, Response, NextFunction } from 'express';
import { injectable, inject } from 'inversify';
import { IUserController } from './IUserController';
import { AppEntity } from '../../enterpriseBusinessRules/entities/AppEntity';
import { UserEntity } from '../../enterpriseBusinessRules/entities/UserEntity';
import { GroupEntity } from '../../enterpriseBusinessRules/entities/GroupEntity';
import { IUserUseCases } from '../../applicationBusinessRules/useCases/IUserUseCases';
import { IOCTypes } from '../../iOC/IOCTypes';
#injectable()
export class UserController implements IUserController {
public userUseCases: IUserUseCases;
constructor(#inject(IOCTypes.IUserUseCases) userUseCases: IUserUseCases) {
this.userUseCases = userUseCases;
}
public async fetchUsers(request: Request, response: Response, next: NextFunction): Promise<void>{
try {
const users: UserEntity[] = await this.userUseCases.fetchUsers(request);
response.status(200).json({
message: 'Users were fetched.',
users,
});
} catch (error) {
next(error);
}
}
}
Whenever I request the route /users the function fetchUsers gets executed. Unforunately my API crashes and throws this error
RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code:
undefined
I debugged and found this error
TypeError: Cannot read property 'userUseCases' of undefined
Within the constructor of the class this is set and works fine. Within the fetchUsers function this is undefined. How can I fix this? Because I have to use this to get access to my userUseCases variable.
Edit: Thanks to Adam Kosmalas comment I was able to fix it by binding the function within the constructor
this.fetchUsers = this.fetchUsers.bind(this)
but I don't know if this is the best solution. Then I would have to bind every function in every class within its constructor...
Any other ideas?

Another options is to use arrow functions for method declarations. This way you don't need to explicitly bind this:
public fetchUsers = async (request: Request, response: Response, next: NextFunction): Promise<void> => {
try {
const users: UserEntity[] = await this.userUseCases.fetchUsers(request);
response.status(200).json({
message: 'Users were fetched.',
users,
});
} catch (error) {
next(error);
}
}

Inside the constructor bind "this" to your function using bind(this) this will make sure It will execute the function in context of "this" of the class.
constructor(#inject(IOCTypes.IUserUseCases) userUseCases: IUserUseCases) {
this.userUseCases = userUseCases;
this.fetchUsers = this.fetchUsers.bind(this)
}
It was not working because your function was executing somewhere else thus it was getting 'this' from there
If you don't want to bind and properties are public then you can directly access if from outside of the service like.
const users: UserEntity[] = await this.nameOfTheImportedService.userUseCases.fetchUsers(request);

Related

Javascript global variable with async/await is set, but later undefined

I'm setting up a mongoDB endpoint with NodeJS. Implementing this backend
I seem to have a problem with the code where the function static async injectDB sets a global variable let restaurants which another function static async getRestaurants accesses, but then it turned into undefined
import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID
let restaurants
export default class RestaurantsDAO {|
static async injectDB(conn) {
if (restaurants) {
return
}
try {
restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
} catch (e) {
console.error(
`Unable to establish a collection handle in restaurantsDAO: ${e}`,
)
}
}
static async getRestaurants({
filters = null,
page = 0,
restaurantsPerPage = 20,
} = {}) {
console.log(restaurants) // undefined
...
getRestaurants is of course called at a much later point than injectDB, if I console.log(restaurants) in that function, it writes out its values. But its undefined when the other function is called. Why is that?
The injectDB function is called at server start, while the getRestaurants is called when someone acceesses the endpoint.
An alternative solution is to open the connection to DB in the getRestaurants function, is that best practice?
See full code for restaurantsDAO.js
Be aware that you cannot know if the await code has finished unless you check for it. It can really help to put console.logs everywhere! See this example:
export default class RestaurantsDAO {
static restaurants
static async injectDB(conn) {
if (RestaurantsDAO.restaurants) {
return
}
try {
console.log("start loading")
RestaurantsDAO.restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
console.log("finished loading restaurants!")
} catch (e) {
console.error(
`Unable to establish a collection handle in restaurantsDAO: ${e}`,
)
}
}
static showRestaurants() {
if (RestaurantsDAO.restaurants) {
console.log("restaurants are loaded")
} else {
console.log("restaurants not yet loaded")
}
}
}
So if you call injectDB anywhere else in your code, that doesn't guarantee that restaurants is filled right away.
import RestaurantsDAO from "./restaurantsdao.js"
RestaurantsDAO.injectDB(..) // console: "start loading"
RestaurantsDAO.showRestaurants() // console: "restaurants not yet loaded"
// console: "finished loading" (because await has finished)
BTW I think it makes more sense if you make the restaurants variable part of the class, instead of defining it outside of the class on the module.

Cannot read property of constructed class

I am fairly new to TypeScript and I have recently run into a problem I can not solve.
I am creating a REST API with Express. I have a router, which calls a controller and inside the controller, I call a method of a service and then return the response.
This is how my router looks:
import express from 'express';
import { BidsController } from '../../controllers/bids.controller';
const router = express.Router();
const bidsController = new BidsController();
router.post('/bids', isAuthenticated, checkRoles(['user']), checkIsVerified, bidsController.createBid);
The router has some middleware, but neither one of them is using bidsController so I do believe they do not cause the error.
This is my bidsController:
import validator from '../validator';
import { BidsService } from '../services/bids.service';
class BidsController implements IBidsController {
bidsService;
constructor() {
this.bidsService = new BidsService();
}
async createBid(req: Request, res: Response, next: NextFunction): Promise<void> {
const { params, body } = req;
try {
validator.bids.create(params, body);
const { userId } = res.locals.tokenInfo;
const { value } = body;
const response = await this.bidsService.createBid(value, userId);
res.status(201).json(response);
} catch (exception) {
next(exception);
}
}
}
This is the Service:
class BidsService implements IBidsService {
public async createBid(value: number, userId: string): Promise<IBid> {
const bid = new Bid({
value,
user: userId,
})
await bid.save();
return bid;
}
}
So, when I use Postman to call the POST /bids endpoint, I get the error:
"TypeError: Cannot read property 'bidsService' of undefined"
Could you please help me solve this issue?
router.post('/bids', isAuthenticated, checkRoles(['user']),
checkIsVerified, bidsController.createBid.bind(bidsController)); // <- THE FIX
First, this is a JS runtime error, not related to TS.
The this keyword is dynamically determined when calling “method” of an object.
Usually you directly call the method on that object, like bidsController.createBid(). This would bind the this keyword inside createBid to bidsController.
However, in your case, you don’t call it directly. Instead you just pass the value of bidsController.createBid, which is a function, to the router.post as a callback, which would be called later.
The this keyword is unbound in this case, because when it’s called at a later time, it doesn’t have any information about bidsController. In order to provide that info, you use bidsController.createBid.bind(bidsController) to bind it beforehand.
The other way to bind ahead of time, is to use arrow function when declaring the class method.
class BidsController implements IBidsController {
createBid = async (req: Request, res: Response, next: NextFunction) => {
const { params, body } = req;

Method in service is undefined when executing but not when logging to console

I have 2 Service classes
UserClass
import axios from "axios";
export default class {
constructor(){
this.http= axios.create({baseURL:"/api/users/"});
}
getUser(userId){
return this.http(userId);
}
}
BusinessClass
import axios from "axios";
import AppState from "../utils/appState";
export default class {
constructor(){
this.http= axios.create({baseURL:"/api/business/"});
this.appState = new AppState();
}
async getAllBusiness(){
try{
let result =await this.http("all");
this.appState.save('all_business', result.data);
}catch(ex){
console.log("AllBusiness",ex);
}
return;
}
}
When I import and create an instance of these in my vue component first one has all it's methods. But the second one loses it's methods in code.
When I put a debug point and log it, it will get logged as method. But when I execute it, it will log an error.
//before export default
import UserService from "../Services/UserService";
import BusinessService from "../Services/BusinessService";
//inside export default
async created(){
this.$UserService = new UserService();
this.$BusinessService = new BusinessService();
let result = await this.$UserService.getUser(this.id); //=> this one works
await this.$BusinessService.getAllBusiness(); //=>this one logs this.$BusinessService.getAllBusiness is not a function
}
I also tried these two ways to define the method
getAllBusiness(){
return new Promise((resolve,reject)=>{
this.http("all")
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
.catch(()=>{reject()});
});//also tried with bind(this)
}
getAllBusiness=()=>{
return new Promise((resolve,reject)=>{
this.http("all")
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
.catch(()=>{reject()});
});//also tried with bind(this)
}
Using console.log(this.$BusinessService.getAllBusiness) in debug will show ƒ getAllBusiness() { ... the content of the code.
But hovering on it while debugging in chrome it will show undefined
It seems the issue is with this line
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
In this you are not returning. Try by returning result from the then.
Also await may need to be inside async function

What is the nestjs error handling approach (business logic error vs. http error)?

While using NestJS to create API's I was wondering which is the best way to handle errors/exception.
I have found two different approaches :
Have individual services and validation pipes throw new Error(), have the controller catch them and then throw the appropriate kind of HttpException(BadRequestException, ForbiddenException etc..)
Have the controller simply call the service/validation pipe method responsible for handling that part of business logic, and throw the appropriate HttpException.
There are pros and cons to both approaches:
This seems the right way, however, the service can return Error for different reasons, how do I know from the controller which would be the corresponding kind of HttpException to return?
Very flexible, but having Http related stuff in services just seems wrong.
I was wondering, which one (if any) is the "nest js" way of doing it?
How do you handle this matter?
Let's assume your business logic throws an EntityNotFoundError and you want to map it to a NotFoundException.
For that, you can create an Interceptor that transforms your errors:
#Injectable()
export class NotFoundInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// next.handle() is an Observable of the controller's result value
return next.handle()
.pipe(catchError(error => {
if (error instanceof EntityNotFoundError) {
throw new NotFoundException(error.message);
} else {
throw error;
}
}));
}
}
You can then use it by adding #UseInterceptors(NotFoundInterceptor) to your controller's class or methods; or even as a global interceptor for all routes. Of course, you can also map multiple errors in one interceptor.
Try it out in this codesandbox.
Nest Js provides an exception filter that handles error not handled in the application layer, so i have modified it to return 500, internal server error for exceptions that are not Http. Then logging the exception to the server, then you can know what's wrong and fix it.
import 'dotenv/config';
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Logger } from '#nestjs/common';
#Catch()
export class HttpErrorFilter implements ExceptionFilter {
private readonly logger : Logger
constructor(){
this.logger = new Logger
}
catch(exception: Error, host: ArgumentsHost): any {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
const statusCode = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
const message = exception instanceof HttpException ? exception.message || exception.message?.error: 'Internal server error'
const devErrorResponse: any = {
statusCode,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
errorName: exception?.name,
message: exception?.message
};
const prodErrorResponse: any = {
statusCode,
message
};
this.logger.log( `request method: ${request.method} request url${request.url}`, JSON.stringify(devErrorResponse));
response.status(statusCode).json( process.env.NODE_ENV === 'development'? devErrorResponse: prodErrorResponse);
}
}
You may want to bind services not only to HTTP interface, but also for GraphQL or any other interface. So it is better to cast business-logic level exceptions from services to Http-level exceptions (BadRequestException, ForbiddenException) in controllers.
In the simpliest way it could look like
import { BadRequestException, Injectable } from '#nestjs/common';
#Injectable()
export class HttpHelperService {
async transformExceptions(action: Promise<any>): Promise<any> {
try {
return await action;
} catch (error) {
if (error.name === 'QueryFailedError') {
if (/^duplicate key value violates unique constraint/.test(error.message)) {
throw new BadRequestException(error.detail);
} else if (/violates foreign key constraint/.test(error.message)) {
throw new BadRequestException(error.detail);
} else {
throw error;
}
} else {
throw error;
}
}
}
}
and then
You could also use a factory or handler to when controller catch the exception (error or domain error) its map it to another HttpException.
#Controller('example')
export class ExampleController {
#Post('make')
async make(#Res() res, #Body() data: dataDTO): Promise<any> {
try {
//process result...
return res.status(HttpStatus.OK).json(result);
} catch (error) {
throw AppErrorHandler.createHttpException(error); //<---here is the error type mapping
};
};
};

Subscribe to observable is returning undefined

So I am trying to subscribe to a simple service that return data from a local JSON file.
I have managed to get the service working, I can log it out in the function, but when I subscribe to the service in the angular 2 component, it is always undefined. I'm not sure why? Any help would be much appreciated.
API service
export class ApiService {
public data: any;
constructor(private _http: Http) {
}
getData(): any {
return this._http.get('api.json').map((response: Response) => {
console.log('in response', response.json()); //This logs the Object
this.data = response.json();
return this.data;
})
.catch(this.handleError);
}
}
Component
export class AppComponent {
public data: any
public informationData;
constructor(private _api: ApiService) {}
public ngOnInit(): void {
console.log(this.getDataFromService()); // This return undefined
}
public getDataFromService() {
this._api.getData().subscribe(response => {
this.informationData = response;
return this.informationData;
});
}
}
Maybe some pictures help?
The numbers here indicate the order of operations.
Send the Http Request
Component is initialized and calls the getMovies method of the movieService.
The movieService getMovies method returns an Observable. NOT the data at this point.
The component calls subscribe on the returned Observable.
The get request is submitted to the server for processing.
The ngOnInit method is complete.
Any code here after the subscribe cannot access the movies property since the data has not yet been returned.
Receive the Http Response
At some LATER point in time ...
The movies are returned to the service.
If the process was successful, the first callback function is executed.
The local movies property is assigned to the movies returned from the service. It is only here that the movies property is finally set.
Attempting to access the movies property prior to step #8 results in an error.
Can we access the value here? NO
To fix it:
objResponse;
this.service.getData().subscribe((result: any)=> {
this.objResponse=result;
}
Returning something won't required
you can do it like this:
In your app-component:
public getDataFromService() {
this._api.getData(this);
}
public setData(data: any){
this.data=data;
}
In your service/api.ts:
public getData(obj: appComponentModel){
this.http.get(url).subscribe(res => obj.setData(res));
}
Try with:
getData(): any {
return this._http.get('api.json');
}
or
getData(): any {
return this._http.get('api.json').map((response: Response) => {
response.json();
})
You've got a problem between sync and async function. You'r issue is: getDateFromService is syncronous and the content inside is async. So when the ngOnInit function call getDataFromService, you'r code don't wait the async task. you'r getDataFromService need to return an observer or need to implement the return of your API (you need to choose).
public ngOnInit(): void {
console.log(this.getDataFromService().subscribe(data => console.log(data)); // This return undefined
}
public getDataFromService() {
return this._api.getData();
}
Instead of logging at the ngOnInit() method as you did
public ngOnInit(): void {
console.log(this.getDataFromService()); // This return undefined }
log inside the subscribe() method as
export class AppComponent {
public data: any
public informationData;
constructor(private _api: ApiService) {}
public ngOnInit(): void {
this.getDataFromService(); //don't log here, logging here will return undefined
}
public getDataFromService() {
this._api.getData().subscribe(response => {
this.informationData = response;
console.log(this.informationData); //log here, like this
return this.informationData;
});
}
}
Imagine 'subscribe' as a separate thread running, write everything that is needed inside an anonymous function inside 'subscribe'. Whenever the 'data' is available, it will be available inside the subscribe method.
Hope this helps.

Categories