LHJ

I'm a FE developer.

20.4 함수 모듈을 통한 모듈 커스터마이징

04 Jun 2020 » js_lj

20.4 함수 모듈을 통한 모듈 커스터마이징

모듈은 대부분 객체를 내보내지만, 이따금 함수 하나만 내보낼 때도 있습니다.
함수 하나만 내보내는 경우는 그 모듈의 함수를 즉시 호출하려는 의도로 만들 때가 대부분입니다.
이런 경우 사용하려는 것은 그 함수가 아니라 함수의 반환값입니다(물론 반환값이 함수일 수 있습니다).
달리 말해, 모듈이 내보내는 함수가 아니라 그 함수가 반환하는 것을 쓰게 한 겁니다.
이런 패턴은 모듈을 일부 커스터마이즈 하거나, 주변 컨텍스트에서 정보를 얻어야 할 때 주로 사용합니다.
실제 쓰이는 npm 패키지 debug를 예로 들어 봅시다.
debug임포트할 때는 문자열 매개변수를 하나 넘깁니다.
이 문자열은 로그 앞에 접두사로 써서 프로그램의 다른 부분과 구별하는 역할을 합니다.
debug 모듈은 이렇게 사용합니다.

const debug = require('debug')('main'); // 모듈이 반환하는 함수를 즉시 호출할 수 있습니다.

debug("starting");      // 디버그가 활성화되어 있으면 "main starting +0ms"
                        // 라는 로그를 남깁니다.

TIP_
debug 모듈로 디버그를 할 때는 환경 변수 DEBUG를 수정합니다.
위 예제라면 DEBUG=main으로 세팅했을 겁니다.
DEBUG=*을 써서 디버그 메시지를 모두 로그에 기록하게 할 수도 있습니다.

위 예제를 보면 debug 모듈이 반환한 것을 즉시 호출했으므로 debug 모듈이 함수를 반환한다는 것을 알 수 있고, 반환값인 함수 역시 함수를 반환하며 최종적으로 반환된 함수는 첫 번째 함수에 넘긴 문자열을 ‘기억’한다는 걸 알 수 있습니다.
요컨대 우리는 main이란 문자열을 사용하도록 debug 모듈을 커스터마이즈 한 겁니다.
debug 모듈을 직접 만들었다면 아마 다음과 같은 형태로 만들었을 겁니다.

let lastMessage;

module.exports = function(prefix) {
    return function(message) {
        const now = Date.now();
        const sinceLastMessage = now - (lastMessage || now);
        console.log(`${prefix} ${message} + ${sinceLastMessage}ms`);
        lastMessage = now;
    }
}

이 모듈이 내보내는 함수는 즉시 호출해서 prefix 값으로 모듈 자체를 커스터마이즈 하도록 설꼐했습니다.
lastMessage는 마지막 메시지를 기록했을 때의 타임스탬프입니다.
이 값을 사용해서 메시지 사이에 시간이 얼마나 지났는지 계산할 수 있습니다.

그러면 자연히 모듈을 여러 번 임포트하면 어떻게 되는가 하는 의문이 생길 겁니다.
예를 들어 우리가 직접 만든 debug 모듈을 두 번 임포트하는 예제를 만들어 봅시다.

let lastMessage;

module.exports = function(prefix) {
    return function(message) {
        const now = Date.now();
        const sinceLastMessage = now - (lastMessage || now);
        console.log(`${prefix} ${message} + ${sinceLastMessage}ms`);
        lastMessage = now;
    }
}

const debug1 = require('./debug')('one');
const debug2 = require('./debug')('two');

debug1('started first debugger!')
debug2('started second debugger!')

setTimeout(function() {
    debug1('after some time...');
    debug2('what happens?');
}, 200)

아마 다음과 같은 결과를 볼 거라고 생각할 겁니다.

one started first debugger! + 0ms
two started second debugger! + 0ms
one after some time... + 200ms
two what happens? + 200ms

하지만 실제 보이는 화면은 이렇습니다(몇 밀리초 오차는 있겠죠).

one started first debugger! + 0ms
two started second debugger! + 0ms
one after some time... + 200ms
two what happens? + 0ms

노드는 노드 앱을 실행할 때 어떤 모듈이든 단 한 번만 임포트합니다.
따라서 debug 모듈을 두번 임포트하더라도, 노드는 해당 모듈을 이미 임포트했음을 인식하고 다시 임포트하지는 않습니다.
debug1과 debug2는 서로 다른 함수이지만, 둘은 같은 lastMessage를 참조합니다.

이 동작 방법은 안전하며 권장할 만한 방식입니다.
성능, 메모리 사용량, 관리 편의성 등에서 모듈은 단 한 번만 임포트하는 것이 낫습니다.

TIP_
npm의 debug 모듈 역시 우리가 만든 모듈과 비슷한 방법으로 동작합니다.
독립적인 디버그 로그를 여러 개 기록하고 싶다면, lastMessage 타임스탬프를 모듈이 반환하는 함수로 옮기기만 하면 됩니다.
이렇게 하면 각 함수마다 서로 다른 타임스탬프를 유지하므로 디버그 로그 역시 독립적으로 운영할 수 있습니다.