평화롭게 프로젝트를 진행하고 있었고 고차 함수를 사용하면서 문제가 발생했다.

DB에서 가져온 영속성 객체를 Domain Entity로 변환하면서 에러가 발생했다.


TypeError가 발생한다.
순간 뭐지? 싶어서 의존성 주입이 제대로 안됐나? 싶어서 각 Mapper 클래스를 다시 확인해 봤는데


전혀 문제가 없다.
두 Mapper 또한 UserModule 내에 존재하는 Provider이기 때문에 의존성 주입이 실패할 이유도 없다.
그래서 nestjs inject dependency but undefined 같은 저질 영어로 구글링을 시도 했다가 나에게 발생한 같은 문제를 다루는 GitHub issue를 발견했다.
"This" is undefined on nested injected provider
이게 처음엔 NestJS의 버그인 줄 알았는데, 이슈 글에서 확인해보니 JS의 고차함수의 버그라는 것을 알았다.
글을 끝까지 읽어 보면 알겠지만 사실 이게 버그라고 하긴 애매하고 내부적인 구현에 따라 생긴 이슈라고 하는 게 맞는 것 같다.

const obj = {
a = 1;
printA() {
console.log(this.a);
}
}
const printA = obj.printA;
printA();
위 결과가 어떻게 될 것 같은가?
당연히 undefined다.
this는 함수 호출 시점에 결정되고, 함수 호출 방법에 따라 값이 달라진다.
위 예시에서
obj.printA();
를 호출하는 경우엔 당연히 1이 우리의 의도대로 출력된다.
obj.printA는 obj라는 객체의 메서드이다.
그렇기 때문에 printA 호출 당시의 this는 obj를 가리킨다.
하지만 다른 변수에 함수를 할당하는 순간 printA는 obj의 메서드가 아닌 그냥 함수가 된다.
그렇게 되면 당연히 우리가 의도한 대로 this 값이 할당되지 않는다.
return records.map(this.mapper.toEntity);
문제가 됐던 코드였다.
콜백에 들어가는 파라미터 값을 그대로 다른 함수나 메서드를 호출할 때 인수 값으로 넣어줄 경우 위처럼 생략해서 코드를 작성할 수 있다.
난 이전 예시처럼 다른 변수에 메서드를 할당한 것도 아니고, 그냥 단순히 map 함수를 호출하고 생략해서 코드를 작성했을 뿐인데 this가 undefined가 됐다.
아무래도
records.map((val) => this.mapper.toEntity(val));
이렇게 콜백 내에서 직접 메서드를 호출하는 게 아니라
records.map(this.mapper.toEntity);
이런 식으로 메서드 자체를 인수로 넘기게 되면 내부적으로 메서드를 함수 참조하고 실행하는 것으로 보인다.
const toEntity = this.mapper.toEntity();
toEtity();
이러니 toEntity 메서드는 그냥 함수가 되고 this는 undefined가 될 수밖에...
아무튼 해결 방법은 다음과 같다.
return records.map((record) => this.mapper.toEntity(record));
이런 식으로 콜백 내에서 직접 메서드를 호출하던지,
return records.map(this.mapper.toEntity.bind(this.mapper));
이렇게 toEntity 함수 내에서 사용될 this를 bind 해주면 된다.
참 개같은 좋은 언어다.