핵심 정리
이 점을 놓치면 EventEmitter를 비동기 큐처럼 오해하기 쉽습니다.
emitter.on("tick", () => {
console.log("listener");
});
emitter.emit("tick");
console.log("after");여기서는 listener가 먼저 실행됩니다. 즉 emit은 등록된 리스너를 즉시 돌립니다.
이벤트 흐름
먼저 구분해 둘 기본형은 아래입니다.
- 계속 듣기:
on(event, listener) - 한 번만 듣기:
once(event, listener) - 리스너 해제:
off(...) - 오류 경로:
error이벤트 emit()은 기본 동기 실행
on vs once
- 계속 듣는 이벤트:
on - 최초 1회만 필요한 이벤트:
once
연결 완료, 초기화 완료 같은 이벤트는 once가 더 자연스러운 경우가 많습니다. 그렇지 않으면 리스너가 쌓이기 쉽습니다.
emitter.once("ready", () => {
console.log("init once");
});error는 특별하다
error 이벤트는 일반 이벤트처럼 흘려보내면 안 됩니다. 리스너가 없는데 error를 emit하면 프로세스 레벨 문제로 커질 수 있습니다.
emitter.emit("error", new Error("failed"));이 흐름은 error 리스너가 없으면 바로 위험해질 수 있습니다.
즉 EventEmitter 카드는 곧 "오류 경로 설계" 카드이기도 합니다.
cleanup도 같이 봐야 한다
리스너는 등록만큼 해제도 중요합니다.
- 오래 사는 emitter
- 요청마다 붙는 리스너
- 일회성 이벤트
이런 상황에서는 off, removeAllListeners, once 선택이 구조 품질에 직접 영향을 줍니다.
emit()은 큐에 넣는 게 아니라 즉시 돌린다
EventEmitter를 비동기 메시지 버스처럼 오해하면 실행 순서를 잘못 읽기 쉽습니다.
emit()은 현재 호출 스택 안에서 등록된 리스너를 바로 실행하므로, 리스너 안의 무거운 작업도 그대로 호출부 지연으로 이어집니다.
사용 기준
체크포인트
emit()은 기본 동기- 일회성 이벤트는
once - 장수 emitter는 listener cleanup 고려
error이벤트 경로를 명시- 무거운 리스너는 호출부 지연으로 이어진다는 점을 같이 본다
주의할 점
EventEmitter는 느슨한 결합 도구이지만, 리스너 추가가 쉬운 만큼 "누가 해제 책임을 지는가"가 흐려지기 쉽습니다. 이벤트 구조가 복잡해질수록 emit 타이밍보다 리스너 수명과 error 경로를 먼저 보는 편이 맞습니다.
참고 링크
1 sources