🗓️ 2023. 12. 10
⏱️ 4

EventEmitter는 비동기가 아니에요

사실 Observer 패턴에 가까워요

흔한 오해

Node.js의 내부 동작 방식을 공부하다 보면 내장 API를 접하게 되는데요, 이때 EventEmitter에 대해 혼동할 수 있는 지점이 있습니다.

비동기 동작을 구현할 때 대부분 EventEmitter를 사용하니까 EventEmitter는 비동기적으로 돌아가는 거겠지?

결론부터 말하자면 EventEmitter는 비동기 동작과 무관하며, 그 구현은 Observer 패턴에 가깝습니다. fshttp같은 내장 모듈들은 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를 거친 이후에 종료됐을 겁니다.

내부 구현 살펴보기

EventEmitterObserver 패턴을 구현하고 있기 때문에 직접 클래스를 만들어 볼 수도 있습니다. 실제 클래스의 구현은 훨씬 더 복잡하지만 간단하게 핵심 동작을 구현해보면 다음과 같습니다.

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의 핵심 컨셉이 이벤트 기반이므로 비동기 코드 또한 이벤트 기반으로 동작한다는 것을 명확히 알고 있어야 합니다.

참고

돌아가기
© 2024 VERYCOSY.