Node.js에서의 전역 객체와 EventEmitter 객체

서건혁·2024년 3월 7일
1
post-thumbnail

Node.js의 전역객체


브라우저에서는 window라는 전역 객체가 존재한다면, Node.js에서는 global 이라는 전역 객체가 존재한다. 그래서 Node.js 터미널에서 현재 터미널의 명령어로 this === global을 입력하면 true 라는 결과가 나오게 된다.

또한 이러한 global 객체는 var 변수를 통해 추가적인 global 객체의 프로퍼티를 만들 수 있다.

process 프로퍼티 또한 global 객체에 있는데

process.argv를 통해서 node 명령어나 npm 명령어를 통해 입력받은 명령어들을 볼 수 있다.

//만약 실행 명령어로 'node ./파일 이름.js 인자1 인자2 인자3' 을 입력했다면
//./파일 이름.js에서
process.argv.forEach(argument => console.log(argunment));
//코드를 통해 입력 명령어가 어떻게 되는지 확인할 수 있다.

fs 같은 경우 node.js 에서 파일 시스템과 소통하기 위한 모듈로서 이러한 파일 시스템 소통 방식은 비동기, 동기 방식 2가지 모두 존재하지만, 성능 상의 이점을 위해 비동기로 구현하는 것이 좋다.

기본적으로는 동기식으로 작동하며 비동기식으로 구현하기 위해서는 util 모듈의 promisify 함수를 써야 한다.

import * as fs from 'fs';

import * as util from 'util'

//기존의 동기식으로 작동하는 writeFile 함수를 Promise 객체를 반환하는 비동기 함수로 바꾼다.
const writeFile = util.promisify(fs.writeFile);

writeFile('./newFile.txt', null)

.then(() => {

console.log('File created successfully');

})

.catch(error => {

console.log(error);

});

EventEmitter 객체

Node.js 핵심 아키텍처는 기본적으로 event-driven 아키텍처에 의지하고 있다. 때문에 Node.js의 많은 오브젝트들이 EventEmmiter 객체로부터 상속된다.

이러한 오브젝트들은 우리가 emitters라고 부르는 events을 생성한다(방출한다라고 하는 게 더 정확할 것이다.) 그리고 우리는 그것들을 listener라고 부르는 콜백 함수를 통해 해당 이벤트들에 대한 상호작용을 할 수 있다.


import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 //EventEmitter 객체로 할당된 eventEmitter에 .on('이벤트 이름', '콜백 함수')함수를 통하여 이벤트를 생성할 수 있다.
eventEmitter.on('event', function() {
  console.log('one');
});
eventEmitter.on('event', function() {
  console.log('two');
});
eventEmitter.on('event', function() {
  console.log('three');
});
//eventEmitter에 등록된 'event' 이벤트를 방출한다.
eventEmitter.emit('event');

eventEmitter는 emit 함수를 통해 등록된 이벤트들을 방출할 때, 방출된 이벤트들은 등록된 순서대로 불러진다.

그렇기에 위에 자바스크립트 파일을 실행하면 다음과 같은 결과가 나온다.

one
two
three

그렇기에 EventEmitter는 동기적으로 함수를 호출하며, emit 함수가 호출될 때 해당 시점에서 등록되지 않은 이벤트들은 호출되지 않는다.

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

const asyncFunction = async () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('비동기 함수 완료');
    }, 1000);
  });
};

//이벤트 등록의 콜백 함수가 비동기 함수
myEmitter.on('myEvent', async () => {
  const result = await asyncFunction();
  console.log(result);
});

//이때 해당 이벤트가 실행이 완료될 때까지 기다린다.(블로킹)
myEmitter.emit('myEvent');
import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 //emit 함수 호출 시점에 아무것도 등록된 이벤트가 없으므로 이벤트를 방출하지 않는다.
eventEmitter.emit('event');
 //밑에 있는 event는 호출되지 않는다.
eventEmitter.on('event', function() {
  console.log('Event occured!');
});

만약 eventEmitter에 화살표 함수를 통해 콜백 함수를 등록하려고 할때 this는 eventEmitter 인스턴스를 참조하는 것이 아닌 외부 스코프를 참조하게 된다.


import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 
eventEmitter.on('event', () => {
  console.log(this === eventEmitter); // this는 global을 가지게 될 것이다. 그러므로 false
});
 
eventEmitter.emit('event');

😁알아두면 좋은 자바스크립트 지식
function 키워드를 통해 선언된 함수와 화살표 함수를 통해 선언된 함수의 this는 차이점이 존재한다.
funcion 키워드를 통해 선언된 함수의 this는 함수가 선언된 곳의 외부 스코프를 참조하고 화살표 함수를 통해 선언된 함수의 this는 해당 함수가 호출되는 곳의 외부 스코프를 참조하게 된다.

eventEmitter의 이벤트를 지우려면 eventEmitter에 .removeListener(’이벤트 이름’, ‘콜백 함수’) 함수를 호출해 특정 이벤트만 지우거나 .removeAllListeners() 함수를 호출해 eventEmitter에 등록된 모든 이벤트들을 지울 수도 있다.

import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 
function listener () {
  console.log('Event occurred!');
}
 
eventEmitter.on('event', listener);
eventEmitter.emit('event');
//이벤트 지우기(특정 이벤트)
eventEmitter.removeListener('event', listener);
//또는 이벤트 지우기(모든 이벤트)
eventEmitter.removeAllListeners();

하지만 이벤트들을 방출할 때 실행되는 콜백함수는 동기적으로 작동되고, 이러한 함수들은 콜스택에 등록되기에 특정 상황에서는 콜스택 오버플로우 문제가 발생할 수도 있다.

import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 
eventEmitter.on('event1', () => {
  console.log('First event here!');
  eventEmitter.emit('event2');
});
 
eventEmitter.on('event2', () => {
  console.log('Second event here!');
  eventEmitter.emit('event3');
});
 
eventEmitter.on('event3', () => {
  console.log('Third event here!');
  eventEmitter.emit('event1');
});
 //Maximum call stack size exceede 에러가 뜬다.
eventEmitter.emit('event1');

때문에 이를 방지하기 위해서 해당 형식으로 이벤트 들을 방출하고 싶다면 비동기 함수의 작동 원리를 이용하여 이벤트 루프를 통해 렌더링 되는 방식으로 콜스택이 무한히 쌓이지 않도록 관리하는 방법을 차용할 수도 있다.

import * as EventEmitter from 'events';
 
const eventEmitter = new EventEmitter();
 //비동기 함수를 통해 콜스택에 쌓이지 않고 태스크 큐에 쌓이고 그것이 떄가 되면
 //이벤트 루프를 통해 렌더링 될 수 있을만한 타이밍(콜스택이 비어있을 때)을 확인하고 렌더링 된다.
eventEmitter.on('event1', () => {
  setTimeout(() => {
    console.log('First event here!');
    eventEmitter.emit('event2');
  })
});
 
eventEmitter.on('event2', () => {
  setTimeout(() => {
    console.log('Second event here!');
    eventEmitter.emit('event3');
  })
});
 
eventEmitter.on('event3', () => {
  setTimeout(() => {
    console.log('Third event here!');
    eventEmitter.emit('event1');
  })
});
 
eventEmitter.emit('event1');

또한 eventEmitter에서 한 번만 쓰고 다시는 쓸 이벤트가 아니라면 .once(’이벤트 이름’, ‘콜백 함수’) 함수를 호출하여 한 번만 사용할 수 있다. emit 함수를 통해 사용된 once 이벤트는 removeEventListener 함수를 호출할 필요없이 자동으로 사라지게 된다.


import * as EventEmitter from 'events';
 
class MyEventEmitter extends EventEmitter {
  counter = 0;
}
 
const eventEmitter = new MyEventEmitter();
 
eventEmitter.once('event', function () {
  console.log(this.counter++);
});
 
eventEmitter.emit('event'); // 0
eventEmitter.emit('event'); // 아무것도 보여지지 않는다.

출처

Node.js TypeScript #1. Modules, process arguments, basics of the File System
(https://wanago.io/2019/02/11/node-js-typescript-modules-file-system/)
Node.js TypeScript #2. The synchronous nature of the EventEmitter
(https://wanago.io/2019/02/18/typescript-node-js-eventemitter/)

0개의 댓글