프로그래밍/NODEJS 강좌 BY GEMINI

주요 내장 모듈 살펴보기 - events: 이벤트 기반 프로그래밍의 핵심 (Java의 이벤트 리스너와 유사점)

lazy_web_devloper 2025. 6. 4. 09:49
728x90
반응형

4. events 모듈: 이벤트 기반 프로그래밍의 심장 ❤️

Node.js의 많은 객체는 '이벤트'를 발생시킵니다. 예를 들어, fs.ReadStream 객체는 파일에서 데이터를 읽을 때마다 'data' 이벤트를 발생시키고, 파일 읽기가 끝나면 'end' 이벤트를 발생시킵니다. http.Server 객체는 클라이언트 요청이 들어올 때마다 'request' 이벤트를 발생시킵니다.

events 모듈은 이러한 이벤트 기반 프로그래밍을 직접 구현하고 활용할 수 있도록 EventEmitter 클래스를 제공합니다. EventEmitter는 특정 종류의 이벤트가 발생했을 때 호출될 함수(리스너)를 등록하고, 해당 이벤트를 발생시키는(emit) 기능을 가진 객체입니다.

Java로 비유하자면, Swing이나 JavaFX에서 UI 컴포넌트의 액션 리스너(ActionListener), 마우스 리스너(MouseListener) 등을 등록하고 처리하는 방식과 유사한 개념을 서버 사이드 객체에 적용한 것이라고 볼 수 있습니다. 옵저버 패턴(Observer Pattern)의 한 형태이기도 합니다.

EventEmitter 클래스의 주요 메소드:

events 모듈을 사용하려면 먼저 EventEmitter 클래스를 가져와야 합니다.

JavaScript
 
const EventEmitter = require('events');

// EventEmitter 인스턴스 생성
const myEmitter = new EventEmitter();
  • emitter.on(eventName, listener) 또는 emitter.addListener(eventName, listener): 지정된 eventName에 대해 listener 함수를 등록합니다. 특정 이벤트가 발생하면 등록된 리스너들이 호출됩니다.
    • eventName: 이벤트의 이름 (문자열 또는 심볼)
    • listener: 이벤트가 발생했을 때 호출될 함수. 이 함수는 이벤트 발생 시 전달된 인자들을 받을 수 있습니다.
  • emitter.once(eventName, listener): eventName에 대해 리스너를 등록하지만, 해당 이벤트가 한 번 발생한 후에는 자동으로 리스너가 제거됩니다. 딱 한 번만 실행되어야 하는 초기화 작업 등에 유용합니다.
  • emitter.emit(eventName[, ...args]): 지정된 eventName의 이벤트를 발생시킵니다. 등록된 리스너들이 동기적으로 호출됩니다. 추가적인 인자(...args)를 전달하면 리스너 함수에 그대로 전달됩니다.
    • 이벤트에 등록된 리스너가 하나라도 있으면 true를 반환하고, 없으면 false를 반환합니다.
  • emitter.removeListener(eventName, listener) 또는 emitter.off(eventName, listener): eventName에 등록된 특정 listener를 제거합니다.
  • emitter.removeAllListeners([eventName]): eventName에 등록된 모든 리스너를 제거합니다. eventName이 제공되지 않으면 모든 이벤트의 모든 리스너를 제거합니다.
  • emitter.listenerCount(eventName): eventName에 등록된 리스너의 개수를 반환합니다.
  • emitter.listeners(eventName): eventName에 등록된 리스너 함수의 배열 복사본을 반환합니다.
  • emitter.eventNames(): 현재 EventEmitter 인스턴스에 리스너가 등록된 모든 이벤트 이름의 배열을 반환합니다.

EventEmitter 사용 예시:

JavaScript
 
const EventEmitter = require('events');

class MyCustomEmitter extends EventEmitter {} // EventEmitter를 상속하여 커스텀 클래스 생성 가능

const customEmitter = new MyCustomEmitter();

// 'greeting' 이벤트에 대한 리스너 등록
customEmitter.on('greeting', (name, message) => {
  console.log(`Hello ${name}, ${message}`);
});

// 'farewell' 이벤트에 대한 리스너 등록 (한 번만 실행)
customEmitter.once('farewell', () => {
  console.log('Goodbye! See you next time.');
});

// 'data' 이벤트에 대한 리스너 등록
const dataListener = (data) => {
  console.log('Data received:', data);
};
customEmitter.on('data', dataListener);

// 이벤트 발생시키기
console.log("Emitting 'greeting' event...");
customEmitter.emit('greeting', 'Alice', 'Welcome to Node.js events!'); // 출력: Hello Alice, Welcome to Node.js events!

console.log("\nEmitting 'farewell' event for the first time...");
customEmitter.emit('farewell'); // 출력: Goodbye! See you next time.

console.log("\nEmitting 'farewell' event for the second time...");
customEmitter.emit('farewell'); // 아무것도 출력되지 않음 (once로 등록했기 때문)

console.log("\nEmitting 'data' event...");
customEmitter.emit('data', { id: 1, value: 'Test Data' }); // 출력: Data received: { id: 1, value: 'Test Data' }

// 'data' 이벤트의 특정 리스너 제거
customEmitter.removeListener('data', dataListener);
console.log("\nEmitting 'data' event after removing listener...");
customEmitter.emit('data', { id: 2, value: 'More Data' }); // 아무것도 출력되지 않음

// 'error' 이벤트의 특별한 처리
// 'error' 이벤트는 특별하게 취급됩니다.
// 만약 EventEmitter가 'error' 이벤트를 발생시키고 해당 이벤트에 대한 리스너가 등록되어 있지 않으면,
// Node.js 프로세스는 예외를 던지고 종료됩니다.
// 따라서 'error' 이벤트에 대한 리스너는 항상 등록하는 것이 좋습니다.
customEmitter.on('error', (err) => {
  console.error('An error occurred!', err);
});

customEmitter.emit('error', new Error('Something went wrong!'));
// 출력: An error occurred! Error: Something went wrong! ...

왜 EventEmitter가 중요한가?

Node.js의 많은 핵심 모듈들이 내부적으로 EventEmitter를 상속받아 만들어졌습니다. 예를 들어,

  • http.Server 객체는 'request', 'connection' 등의 이벤트를 발생시킵니다.
  • fs.ReadStream 객체는 'data', 'end', 'error' 등의 이벤트를 발생시킵니다.
  • process.stdin (표준 입력 스트림)도 'data' 이벤트를 발생시킵니다.

따라서 EventEmitter의 동작 방식을 이해하는 것은 Node.js의 비동기적이고 이벤트 중심적인 특성을 활용하는 데 매우 중요합니다. 여러분이 직접 만드는 모듈이나 클래스에서도 특정 상태 변화나 작업 완료를 알리기 위해 EventEmitter를 상속하여 커스텀 이벤트를 정의하고 사용할 수 있습니다.

이는 Java에서 인터페이스를 정의하고 구현 클래스에서 특정 메소드를 호출하여 콜백을 실행하는 방식, 또는 Guava의 EventBus와 같은 라이브러리를 사용하는 것과 유사한 목적을 달성할 수 있게 해줍니다.

728x90
반응형