Node.js의 EventEmitter

Posted by yunki kim on October 1, 2021

  EventEmitter는 node.js에 내장되 있는 옵저버 패턴의 구현이다. 어떤 종류의 객체를 이벤트 이름으로 정의된 특정 이벤트에 정기적으로 전달하는 리스너로 불리는 함수 객체를 실행한다. 예를 들어 fs.ResdStream은 파일을 열 때마다 이벤트를 호출한다. 

  이벤트를 내보내는 객체는 모두 EventEmitter 클래스의 인스턴스이다. 이 객체는 하나 이상의 함수를 이벤트로 사용할 수 있게 이름을 넣어 추가하는 eventEmitter.on()함수를 사용할 수 있다. 이 이벤트의 이름은 js 프로퍼티 키로 사용할 수 있는 모든 문자열이다. 

  EventEmitter 객체로 이벤트를 호출할 때 해당 이벤트에 붙어 있는 모든 함수는 동기적으로 호출되며 호출을 받은 리스너가 반환한 결과는 어떤 값이든 무시된다. 

  EventEmitter 인스턴스를 단일 리스너와 함께 작성한 예는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const EventEmitter = require("events");
const util = require("util");
 
function MyEmitter() {
  EventEmitter.call(this);
}
//어떤 객체이든 EventEmitter를 상속 받을 수 있다.
//inherits()는 프로토타입 상속을 하는 전통적인
//node.js스타일이다.
util.inherits(MyEmitter, EventEmitter);
const myEmitter = new MyEmitter();
myEmitter.on("event", () => {
  console.log("event");
});
myEmitter.emit("event");
cs

  위의 코드에서 ES6클래스 문법으로 다음과 같이 사용할 수 있다.

 

1
2
3
4
5
6
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on("event", () => {
  console.log("event");
});
myEmitter.emit("event");
cs

 

인자와 this를 리스너에 전달하기.

  eventEmitter.emit() 메소드는 인자로 받은 값을 리스터 함수로 전달한다. 이 때 EventEmitter를 통해 호출되는 리스너 함수 내에서는 this가 이 리스너 함수를 부착한 EventEmitter를 참조하도록 의도적으로 구현되 있다. 하지만 arrow function을 사용하면 this는 더이상 EventEmitter의 인스턴스를 참조하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const EventEmitter = require('events');
 
class MyEmitter extends EventEmitter {}
 
const myEmitter = new MyEmitter();
myEmitter.on('event'function (a, b) {
  console.log(a, b, this);
});
myEmitter.emit('event''a''b');
//출력: 
//a b MyEmitter {  _events: [Object: null prototype] { event: [Function (anonymous)] },
//  _eventsCount: 1,
//  _maxListeners: undefined,
//  [Symbol(kCapture)]: false
//}
 
cs

 

동기, 비동기

  순수 js는 동기적으로 작동하며 한번에 하나의 프로세스만 작동시킬 수 있다. js를 비동기적으로 작동시키는 것은 js엔진 바깥을 감싸는 호스팅 환경이다. 

  Node.js가 비동기적으로 작동하는 이유는 내부에 비동기 이벤트를 소화하기 위한 라이브러리인 libuv가 존재하기 때문이다. Node.js의 이벤트 루프는 libuv를 이용해 구현되 있고 크게 여섯 단계의 페이즈를 순환하고 있다. 각 페이즈는 libuv를 통해 커널 또는 쓰레드 풀에 인계했던 콜백작업을 실행한다. 따라서 모든 비동기 작업은 libuv를 통해 스케줄링 되었다가 이벤트 루프의 순환 주기에 따라 호출된다.

  EventListener는 모든 리스너를 등록한 순서대로 동기적으로 처리한다. 이벤트를 적절한 순서로 처리하는 것을 보장해 경쟁 조건(race condition)이나 로직 오류를 피하는 것이 중요하다. 이 모든것들이 충족되었다면 리스너 함수를 비동기로 동작하게 할 수 있다.

1
2
3
4
5
6
7
8
9
10
const EventEmitter = require('events');
 
class MyEvent extends EventEmitter {}
const myEvent = new MyEvent();
myEvent.on('event', () => {
  setTimeout(() => {
    console.log('async event');
  }, 100);
});
myEvent.emit('event');
cs

 

단 한번만 동작하는 이벤트

  eventEmitter.on()으로 등록한 이벤트의 경우 이벤트를 호출하는 횟수만큼 호출된다. 만약 단 한번만 호출하고 싶다면 eventEmitter.on()대신에 eventEmitter.once()만 사용하면 된다. 

 

오류 이벤트

  EventEmitter 인스턴스에서 오류가 발생했다면 error이벤트를 호출하는 것이 전형적인 동작이다. 만약 오류가 발생한 객체에 error이벤트로 등록된 리스너가 없다면 오류가 throw되고 스택 추적이 출력된 후 프로세스가 종료된다. 이러한 종료를 막기 위해서는 error이벤트를 등록하면 된다. 

1
2
3
4
5
6
7
8
const EventEmitter = require('events');
 
class MyEvent extends EventEmitter {}
const myEvent = new MyEvent();
myEvent.on('error', (err) => {
  console.log('error');
});
myEvent.emit('error'new Error('err'));
cs

 

EventEmitter

  EventEmitter는 events모듈에 의해 정의되고 제공된다. 모든 EventEmitter는 새로운 이벤트를 등록할 때마다 newListener이벤트를 호출하고 제거할 때마다 removeListener를 호출한다. newListener 이벤트에 리스너가 전달되기 위해 이벤트 명칭과 추가될 리스너의 참조가 전달된다.

  리스너가 추가되기 전에 newListener 이벤트가 호출된다는 점으로 인해 다음과 같은 부작용이 발생할 수 있다. 동일한 명칭의 리스너를 newListener 콜백에 먼저 등록한다면 추가하려는 해당 리스너가 실제로 등록되기 전에 이 함수가 먼저 추가된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const EventEmitter = require('events');
 
class MyEvent extends EventEmitter {}
const myEvent = new MyEvent();
myEvent.once('newListener', (event, listener) => {
  if(event === 'event') {
    myEvent.on('event', () => {
      console.log('newListener');
    })
  }
})
myEvent.on('event', () => {
  console.log('event');
});
myEvent.emit('event');
//출력:
//newListener
//event
cs

 

참고:https://www.huskyhoochu.com/nodejs-eventemitter/

 

Nodejs EventEmitter 뜯어보기

Node.js 소스 코드를 살펴보면서 EventEmitter가 어떻게 돌아가는지 확인합니다

www.huskyhoochu.com

https://edykim.com/ko/post/events-eventemitter-translation-in-node.js/

 

Node.js의 Events `EventEmitter` 번역

는 Node.JS에 내장되어 있는 일종의 옵저버 패턴 구현이다. node 뿐만 아니라 대부분의 프레임워크나 라이브러리에서 이 구현을 쓰거나 유사한 구현을 활용하고 있는 경우가 많다. DOM Event Listener…

edykim.com