updated_at: 2025-09-19 17:18

Node.js EventEmitter 완벽 가이드

EventEmitter는 Node.js의 핵심 기능 중 하나로, 이벤트 기반(Event-driven) 아키텍처를 구현할 수 있게 해주는 강력한 도구입니다. 이 가이드에서는 EventEmitter의 개념과 사용법을 알아봅니다.

1. EventEmitter란? (핵심 개념)

EventEmitter를 가장 쉽게 이해하는 방법은 **"방송국 시스템"**에 비유하는 것입니다.

  • 이벤트 발생 (emit): 방송국이 특정 주제(이벤트 이름)로 프로그램을 송출하는 행위입니다. 이때 특정 데이터(값)를 함께 실어 보낼 수 있습니다.
  • 이벤트 구독 (on): 시청자가 특정 채널(이벤트 이름)을 구독하고 방송이 시작되기를 기다리는 행위입니다.
  • 이벤트 리스너 (Listener / 핸들러): 방송이 송출되면, 해당 채널을 구독하고 있던 시청자가 수행하는 **행동(콜백 함수)**입니다.

이 구조를 통해, 코드의 한 부분(발행자)이 다른 부분(구독자)을 직접 호출하지 않고도, "이벤트"라는 신호를 통해 느슨하게 상호작용할 수 있습니다. 이를 **느슨한 결합(Loose Coupling)**이라고 하며, 유연하고 확장 가능한 소프트웨어를 만드는 핵심 원칙입니다.

2. 기본 사용법

가장 기본적인 사용법은 EventEmitter 인스턴스를 직접 만들어 사용하는 것입니다.

const EventEmitter = require("events");

// 1. 방송국(EventEmitter) 인스턴스 생성
const myEmitter = new EventEmitter();

// 2. 'news' 채널 구독 신청
//    'news' 이벤트가 발생하면, 전달된 데이터를 받아 콘솔에 출력하는 리스너 등록
myEmitter.on("news", (data) => {
  console.log("새로운 소식을 받았습니다:", data);
});

// 3. 'news' 채널로 방송 송출 (이벤트 발생)
console.log("방송을 시작합니다...");
myEmitter.emit("news", {
  title: "Node.js 업데이트",
  content: "새로운 버전이 출시되었습니다.",
});
console.log("방송을 종료합니다.");

// 실행 결과:
// 방송을 시작합니다...
// 새로운 소식을 받았습니다: { title: 'Node.js 업데이트', content: '새로운 버전이 출시되었습니다.' }
// 방송을 종료합니다.

3. 클래스와 함께 사용하기 (상속)

더 실용적인 방법은, 특정 역할을 하는 클래스가 EventEmitter상속하여, 스스로 이벤트를 발생시키는 능력을 갖게 하는 것입니다.

예제 시나리오

파일을 처리하는 FileProcessor라는 클래스가 있다고 가정해 봅시다. 이 클래스는 처리 시작, 진행 상황, 완료, 오류 등의 상태를 외부로 알리고 싶어 합니다.

3.1. 클래스 정의 (이벤트 발생자)

FileProcessor 클래스가 EventEmitter를 상속받아, 처리 과정에서 start, progress, done, error 이벤트를 emit 하도록 만듭니다.

FileProcessor.js

const EventEmitter = require("events");

class FileProcessor extends EventEmitter {
  constructor(filePath) {
    super(); // 부모 클래스(EventEmitter)의 생성자 호출은 필수입니다.
    this.filePath = filePath;
  }

  process() {
    // 1. 'start' 이벤트 발생
    this.emit("start", this.filePath);

    let progress = 0;
    const interval = setInterval(() => {
      progress += 25;

      // 2. 'progress' 이벤트 발생 (진행률 데이터와 함께)
      this.emit("progress", progress);

      if (progress >= 100) {
        clearInterval(interval);

        // 3. 'done' 이벤트 발생
        this.emit("done", { status: "success", processedFile: this.filePath });
      }
    }, 500);

    // 가상의 오류 상황
    if (!this.filePath) {
      clearInterval(interval);
      // 4. 'error' 이벤트 발생
      this.emit("error", new Error("파일 경로가 제공되지 않았습니다."));
    }
  }
}

module.exports = FileProcessor;

3.2. 클래스 사용 (이벤트 구독자)

이제 다른 파일에서 FileProcessor를 가져와 사용하면서, 각 이벤트에 대한 리스너를 등록합니다.

main.js

const FileProcessor = require("./FileProcessor.js");

const filePath = "my-document.txt";
const processor = new FileProcessor(filePath);

// [구독] 'start' 이벤트가 발생하면 실행될 리스너
processor.on("start", (path) => {
  console.log(`파일 처리 시작: ${path}`);
});

// [구독] 'progress' 이벤트가 발생하면 실행될 리스너
processor.on("progress", (percent) => {
  console.log(`진행률: ${percent}%`);
});

// [구독] 'done' 이벤트가 발생하면 실행될 리스너
processor.on("done", (result) => {
  console.log(`처리 완료! 결과:`, result);
});

// [구독] 'error' 이벤트가 발생하면 실행될 리스너
processor.on("error", (err) => {
  console.error(`오류 발생: ${err.message}`);
});

// 파일 처리 시작!
processor.process();

주요 메소드

  • emitter.on(eventName, listener): 지정된 이벤트(eventName)를 구독합니다. 이벤트가 발생할 때마다 listener 함수가 호출됩니다.
  • emitter.emit(eventName, [...args]): 지정된 이벤트(eventName)를 발생시킵니다. 이벤트와 함께 추가적인 데이터(...args)를 전달할 수 있습니다.
  • emitter.once(eventName, listener): 이벤트를 단 한 번만 구독합니다. 리스너가 한번 실행된 후에는 자동으로 구독이 해제됩니다.
  • emitter.off(eventName, listener) 또는 emitter.removeListener(...): 등록했던 리스너를 제거합니다.

왜 사용하는가? (이점)

  • 관심사의 분리 (Separation of Concerns):
    • FileProcessor는 파일을 처리하는 자신의 핵심 로직에만 집중하고, "상황을 알리는" 역할만 합니다.
    • main.js는 처리 결과를 "어떻게 보여줄지(콘솔 출력 등)"에만 집중합니다.
  • 유연성: main.js를 수정하지 않고도, FileProcessor의 처리 방식(예: 진행률 간격 변경)을 자유롭게 바꿀 수 있습니다. 반대의 경우도 마찬가지입니다.
  • 재사용성: FileProcessor 클래스는 콘솔 앱, 웹 서버, 데스크톱 앱 등 어떤 환경에서든 재사용할 수 있습니다. 사용하는 쪽에서 이벤트만 적절히 처리해주면 됩니다.
평점을 남겨주세요
평점 : 2.5
총 투표수 : 1

질문 및 답글