Post

Javascript 예외 처리 트러블 슈팅기

클래스 A를 상속받은 자식 클래스 B에서, 클래스 A에 대한 instanceof 연산이 false가 되는 문제가 발생했습니다.

Javascript 예외 처리 트러블 슈팅기

서론

authorization header로 전달되는 jwt를 verify하여 이를 request body에 포함시키는 로직을 작성하는 도중 아래와 같은 문제가 발생하였습니다.

  • Authorization 필드가 누락된 경우, Exception을 던져 ErrorMiddleware 에서 처리할 수 있도록 위임합니다.
  • ErrorMiddleware 에서는 예측 가능한 custom exception의 인스턴스인지를 판단하고 예상 가능했던 예외인지 판단하고 response에 이를 포함시킵니다.
  • 하지만, CustomException을 throw 하였음에도, 500 internal server error가 발생하였습니다.

문제의 Exception 클래스들

예상 가능한 예외 상황을 APIError 클래스를 base로 하는 Exception으로 정의하였습니다.

1
2
3
4
5
6
7
export class APIError extends Error {
    statusCode: number
    errorCode: number
    message: string
    cause: Error | string
	... 생략 ...
}
1
2
3
4
5
6
7
export class NotFoundTokenException extends APIError {
    constructor(cause: Error | string = null) {
        super(403, 4030, 'No Token provided', cause)
        Object.setPrototypeOf(this, NotFoundTokenException)
        Error.captureStackTrace(this, NotFoundTokenException)
    }
}

throw new NotFoundTokenException 으로 던져진 예외가, error를 처리하는 middleware로 전달되었지만 알 수 없는 이유로 error instanceof APIError 가 false로 평가되었습니다.

1
2
3
4
5
export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
    const token = req.headers['authorization']?.split(' ')[1]
    if (!token) {
        throw new NotFoundTokenException(new Error('check the "authorization" header field'))
    }

첫번째 시도

The order of middleware loading is important: middleware functions that are loaded first are also executed first.
출처

처음에는 throw된 exception이 error middleware에 전달되지 않아 발생한 문제점으로 인지하였습니다. 이를 먼저 점검해보았고, 문제의 소지가 없어보였습니다.

express middleware는 미들웨어를 처리하는 도중, 에러가 발생할 경우 가장 가까운 error handling middleware를 찾아 에러를 전달합니다.

image-20241220023829317

이러한 플로우에 따라서 앱이 동작하는지 디버거를 통해서 확인하여 error middleware가 정상적으로 동작한다는 것을 보장할 수 있었습니다.

두번째 시도

문제의 원인은 errorAPIError의 instance로 인식하지 못하는 것이었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const errorMiddleware = (error: APIError, req: Request, res: Response, next: NextFunction) => {
    try {
        if (!(error instanceof APIError)) {
            error = new InternalServerError(error)
        }
        res.meta = res.meta ? { ...res.meta, error } : { error }
        res.status(error.statusCode).json({
            message: error.message,
            code: error.errorCode,
        })
    } catch (err) {
        logger.error('fail in error middleware', { original: error, new: err })
        res.status(500).json({ message: 'internal server error', code: 500 })
    }
}

3-4번 라인에서 error가 APIError의 인스턴스임에도 false로 평가하여 InternalServerError 로 처리된 것이었습니다.

자바스크립트에서는 상속을 프로토타입으로 구현합니다. APIError를 상속받은 NotFoundTokenException 의 프로토타입을 확인해야했습니다. 참고

너무나도 당연하게 Object.setPrototypeOf(this, NotFoundTokenException.prototype) 으로 설정되어 있어야하는 값이 NotFoundTokenException 으로 지정되어있었습니다. 이에 따라, prototype chain을 순회하면서 errorAPIError의 인스턴스인지 확인하는 과정에서 false로 평가되었던 것이었습니다.

이런 문제가 다시는 발생하지 않도록..

해당 문제는 결국 개발 과정에서 발생한 실수 때문이었습니다.

조금 더 신경을 써서 작업을 한다면 실수를 줄일 수 있겠지만, 원천적으로 예방할 수는 없다고 생각했습니다.

instanceof를 자바스크립트에서는 어떤 방식으로 내부적으로 처리하는지 찾아보던 도중, 문제 지점을 줄일 수 있었고 빠르게 수정할 수 있었습니다.

이처럼, 문제가 발생했을 때 문제가 발생한 지점을 빠르게 좁혀나갈 수 있도록 많은 경험을 기록하며 축적하는 것이 최선이라 생각합니다.

This post is licensed under CC BY 4.0 by the author.