흔한 오해
Node.js의 내부 동작 방식을 공부하다 보면 내장 API를 접하게 되는데요, 이때 EventEmitter
에 대해 혼동할 수 있는 지점이 있습니다.
비동기 동작을 구현할 때 대부분 EventEmitter를 사용하니까 EventEmitter는 비동기적으로 돌아가는 거겠지?
결론부터 말하자면 EventEmitter
는 비동기 동작과 무관하며, 그 구현은 Observer
패턴에 가깝습니다. fs나 http같은 내장 모듈들은 EventEmitter
를 활용해서 비동기 프로그래밍을 쉽게 만들어줄 뿐입니다.
코드로 한번 확인해볼까요?
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
emitter.on('hello', () => {
console.log('Hello, World!');
});
// emitter.emit('hello');
EventEmitter 객체에 사용된 on
이 주는 뉘앙스를 파악하는 게 중요합니다. on
은 대기보다는 등록의 의미입니다. 코드에서 emitter 객체는 hello라는 이벤트가 등록됐을 뿐이지, hello라는 이벤트를 대기하고 있는 것이 아닙니다. 그래서 이 코드를 실행하면 프로그램은 곧바로 종료됩니다. 만약 이벤트를 대기하고 있다면 곧바로 종료되지 않고 I/O 요청이나 타이머 함수처럼 Event Loop를 거친 이후에 종료됐을 겁니다.
내부 구현 살펴보기
EventEmitter
가 Observer
패턴을 구현하고 있기 때문에 직접 클래스를 만들어 볼 수도 있습니다. 실제 클래스의 구현은 훨씬 더 복잡하지만 간단하게 핵심 동작을 구현해보면 다음과 같습니다.
class EventEmitter {
listeners = {};
addEventListener(eventName, fn) {
this.listeners[eventName] = this.listeners[eventName] || [];
this.listeners[eventName].push(fn);
return this;
}
on(eventName, fn) {
return this.addEventListener(eventName, fn);
}
emit(eventName, ...args) {
let fns = this.listeners[eventName];
if (!fns) return false;
fns.forEach((f) => {
f(...args);
});
return true;
}
...
}
on 메서드로 이벤트를 등록하고, emit 메서드로 등록된 리스너 함수를 찾아 호출시키는 게 전부입니다. 생각보다 훨씬 더 단순하죠? 비동기와 관련된 코드는 하나도 안 보이네요!
이벤트 기반(Event Driven)
요컨대 EventEmitter
는 Node.js의 핵심 컨셉인 이벤트 기반 프로그래밍을 이루기 위한 클래스입니다. 그 구현은 옵저버 패턴에 가깝구요! Node.js 공식 홈페이지에서는 Node.js를 이렇게 설명합니다.
an asynchronous event-driven JavaScript runtime
비동기적인, 이벤트 기반, 자바스크립트 런타임
비동기와 이벤트 기반. 이 두 키워드는 Node.js를 설명할 때 늘 함께 따라다니기 때문에 쉽게 헷갈릴 수 있는데요, Node.js의 핵심 컨셉이 이벤트 기반이므로 비동기 코드 또한 이벤트 기반으로 동작한다는 것을 명확히 알고 있어야 합니다.