ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Node.js 이벤트이미터(Eventemitter)란?
    NodeJS 2022. 5. 8. 19:23

    들어가며

    이벤트가 갖는 의미는 무엇일까?

    애플리케이션안에서 발생한 응답 가능한 사건을 말한다.

    Node JS 한정 개념은 아니지만 Node JS의 아키텍처에 그간을 이루는 중요한 개념이다.

    Node JS에서의 이벤트

    시스템 이벤트와 event 모듈의 EventEmitter로 발생하는 이벤트 이루어져 있다.

     

    각각 무엇인지 알아보면,

     

    시스템 이벤트

    libuv 라이브러리가 적용된 C++ 코어에서 타이머 작업 혹은 DB 커넥션, IO, HTTP 등을 처리한다.

    libuv 이벤트 루프에는 6개의 페이즈가 있는데 시스템 이벤트의 종류에 따라 각 페이즈에 분류되어 처리된다.

     

    EventEmitter

    옵저버 패턴으로 디자인되어 있으며, 개발자가 실제로 이벤트를 만들고 이벤트를 발생시킬 수 있도록 만들어져 있다.

    EventEmitter의 Pseudocode

    class EventEmitter {
      constructor() {
        this.events = {};
      }
      
      on(type, listener) {
        this.events[type] = this.events[type] || [];
        this.events[type].push(listener);
      }
    }

    생성자는 this.events라는 객체를 초기화하고

    on함수를 추가하여 이벤트의 타입과 리스너를 인자로 받아 Emitter객체에 추가하는 역할을 한다.

    type 키에 리스너들을 리스트 형태로 저장한다.

     

    실제로 적용된 코드를 보면,

    const emitter = new Emitter();
    
    emitter.on('event', () => {
      console.log('Event occurred.');
    });
    

    on 함수는 특정 상황에 이벤트를 등록한다는 의미이다.

     

     

    이제 등록한 이벤트들을 특정 상황에 발생시킬 수 있도록 하는 emit 메서드를 작성하면,

    class EventEmitter {
      ...
      emit(type) {
        if (this.events[type]) {
    	    events[type].forEach((listener) => {
    	      listener();
    	    });
        }
      }
    }
    

    Emitter객체 안에 담겨 있는 타입키의 리스너 배열을 순회하며 리스너를 호출한다.

     

    위의 on, emit 중 이벤트 이미터의 메서드이고 중복 호출을 막는 once라는 메서드도 제공한다.

     

     

    이제 실제 노드 코드를 보면서 이해해보자

    코드 블록을 기준으로 설명해놓았다.

    on 

    function _addListener(target, type, listener, prepend) {
      let m;
      let events;
      let existing;
    
      checkListener(listener);
    
      events = target._events;
      if (events === undefined) {
        // 한번도 리스너 등록한 적 없는 경우 초기화한다.
        events = target._events = ObjectCreate(null);
        target._eventsCount = 0;
      } else {
        if (events.newListener !== undefined) {
          target.emit('newListener', type,
                      listener.listener ?? listener);
    
          events = target._events;
        }
        existing = events[type];
      }
    
      // 최적화를 위해 해당 이벤트의 리스너가 없으면 배열이 아닌 하나의 리스너 오브젝트로 추가한다.
      if (existing === undefined) {
        events[type] = listener;
        ++target._eventsCount;
      } else {
        // 해당 타입의 리스너가 이미 하나 존재하면 Array 형태로 바꿔준다.
        if (typeof existing === 'function') {
          existing = events[type] =
            prepend ? [listener, existing] : [existing, listener];
        } else if (prepend) {
          existing.unshift(listener);
        } else {
          existing.push(listener);
        }
    
        // 헤당 이벤트의 최대 리스너 개수(10개)를 초과 하면 메모리릭을 방지하기 위해 에러를 발생한다.
        m = _getMaxListeners(target);
        if (m > 0 && existing.length > m && !existing.warned) {
          existing.warned = true;
          const w = genericNodeError(
            `Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
            `added to ${inspect(target, { depth: -1 })}. Use emitter.setMaxListeners() to increase limit`,
            { name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
          process.emitWarning(w);
        }
      }
    
      return target;
    }
    
    /**
     * Adds a listener to the event emitter.
     * @param {string | symbol} type
     * @param {Function} listener
     * @returns {EventEmitter}
     */
    EventEmitter.prototype.addListener = function addListener(type, listener) {
      return _addListener(this, type, listener, false);
    };
    
    EventEmitter.prototype.on = EventEmitter.prototype.addListener;

     

    emit

    EventEmitter.prototype.emit = function emit(type, ...args) {
      let doError = (type === 'error');
    
      const events = this._events;
    
      ...
      // 예외를 처리하는 로직
      ...
    
      // type에 매핑되는 최적화된 리스너를 가져온다.
      const handler = events[type];
    
      if (handler === undefined)
        return false;
    
      // 하나의 리스너일 경우 해당 리스너를 call
      if (typeof handler === 'function') {
        const result = handler.apply(this, args);
    
        if (result !== undefined && result !== null) {
          addCatch(this, result, type, args);
        }
      } else {
        // 리스너 리스트일 경우 하나씩 순회하며 리스너를 call
        const len = handler.length;
        const listeners = arrayClone(handler);
        for (let i = 0; i < len; ++i) {
          const result = listeners[i].apply(this, args);
    
          if (result !== undefined && result !== null) {
            addCatch(this, result, type, args);
          }
        }
      }
    
      return true;
    };

     

    Refs.

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

    https://github.com/nodejs/node/blob/master/lib/events.js

    댓글