반응형
상태 변화가 일어날 때 관찰자(listeners)들에게 통지할 수 있는 객체 정의를 통해 패턴을 구성함
EventEmitter Class
EventEmitter 생성 및 사용
import { EventEmitter } from 'events'
import { readFile } from 'fs'
function findRegex (files, regex) {
const emitter = new EventEmitter()
for (const file of files) {
readFile(file, 'utf8', (err, content) => {
if (err) {
return emitter.emit('error', err)
}
emitter.emit('fileread', file)
const match = content.match(regex)
if (match) {
match.forEach(elem => emitter.emit('found', file, elem))
}
})
}
return emitter
}
findRegex(
['fileA.txt', 'fileB.json'],
/hello \w+/g
)
.on('fileread', file => console.log(`${file} was read`))
.on('found', (file, match) => console.log(`Matched "${match}" in ${file}`))
.on('error', err => console.error(`Error emitted ${err.message}`))
- on(event, listener) : 주어진 이벤트 유형에 대해 새로운 리스너를 등록
- once(event, listener) : 첫 이벤트가 전달된 후 제거되는 새로운 리스너를 등록
- emit(event, [arg1], [...]) : 새 이벤트를 생성하고 리스너에게 전달할 추가적인 인자들을 제공한다.
- removeListener(event, listener) : 지정된 이벤트 유형에 대한 리스너를 제거한다.
오류 전파
EventEmitter에서는 콜백처럼 에러를 단지 throw 할 수 없다.
대신 error라는 특수한 이벤트를 발생시키고 Error 객체를 인자로 전달하는 패턴이 있다.
if (err) {
return emitter.emit('error', err)
}
관찰 가능한 객체 만들기
EventEmitter 자체로 사용하는 경우보다 이를 상속받은 class를 사용하는 경우가 대부분이다.
import { EventEmitter } from 'events'
import { readFile } from 'fs'
class FindRegex extends EventEmitter {
constructor (regex) {
super()
this.regex = regex
this.files = []
}
addFile (file) {
this.files.push(file)
return this
}
find () {
for (const file of this.files) {
readFile(file, 'utf8', (err, content) => {
if (err) {
return this.emit('error', err)
}
this.emit('fileread', file)
const match = content.match(this.regex)
if (match) {
match.forEach(elem => this.emit('found', file, elem))
}
})
}
return this
}
}
const findRegexInstance = new FindRegex(/hello \w+/)
findRegexInstance
.addFile('fileA.txt')
.addFile('fileB.json')
.find()
.on('found', (file, match) => console.log(`Matched "${match}" in file ${file}`))
.on('error', err => console.error(`Error emitted ${err.message}`))
EventEmitter와 메모리 누수
관찰 가능 객체에 대해 오랜 시간 동안 구독을 하고 있을 때 필요하지 않으면 구독을 해지하는 것이 중요하다.
const thisTakesMemory = 'A big string...';
const listener = () => {
console.log(thisTakesMemory)
}
emmiter.on('an_event', listener)
emmiter.removeListener('an_event', listener)
EventEmitter는 메모리 누수에 대한 문제 때문에 이벤트의 개수를 10개로 제한하고 있으며
사용자가 setMaxListeners() API를 통해 제한을 조정할 수 있다.
동기/비동기 이벤트
이벤트 발생은 callback과 마찬가지로 동기/비동기를 하나의 EventEmitter에서 혼용하면 안된다.
1. 이벤트의 비동기 발생
현재의 스택이 이벤트 루프에 넘어가기 전까지는 새로운 이벤트 등록이 가능하다.
2. 이벤트의 동기 발생
모든 리스너를 작업실행 전 등록해야 한다.
이벤트 동기 발생 실행 예시
import { EventEmitter } from 'events'
import { readFileSync } from 'fs'
class FindRegexSync extends EventEmitter {
constructor (regex) {
super()
this.regex = regex
this.files = []
}
addFile (file) {
this.files.push(file)
return this
}
find () {
for (const file of this.files) {
let content
try {
content = readFileSync(file, 'utf8')
} catch (err) {
this.emit('error', err)
}
this.emit('fileread', file)
const match = content.match(this.regex)
if (match) {
match.forEach(elem => this.emit('found', file, elem))
}
}
return this
}
}
const findRegexSyncInstance = new FindRegexSync(/hello \w+/)
findRegexSyncInstance
.addFile('fileA.txt')
.addFile('fileB.json')
// this listener is invoked
.on('found', (file, match) => console.log(`[Before] Matched "${match}"`))
.find()
// this listener is never invoked
.on('found', (file, match) => console.log(`[After] Matched "${match}"`))
find()로 실행한 이후에 오는 on()에 대해서는 이벤트가 등록되지 않는다.
비동기로 통일하자!
EventEmitter는 비동기적 이벤트를 다루는데 근거한다.
즉 이벤트의 동기적 발생은 Zalog라 볼 수 있다.
EventEmitter VS Callback
/* eslint handle-callback-err: 0 */
import { EventEmitter } from 'events'
function helloEvents () {
const eventEmitter = new EventEmitter()
setTimeout(() => eventEmitter.emit('complete', 'hello world'), 100)
return eventEmitter
}
function helloCallback (cb) {
setTimeout(() => cb(null, 'hello world'), 100)
}
helloEvents().on('complete', message => console.log(message))
helloCallback((err, message) => console.log(message))
Callback
- 결과가 비동기적으로 반환되야 하는 경우
- 여러 유형의 결과 전달에 있어서 제한이 있음
EventEmitter
- 발생한 사건과 연관시 사용
- 같은 이벤트가 여러번 혹은 0번 발생하는 경우에 사용
결합
대표적인 예시로 glob 패턴의 파일 읽기 처리가 있는데 이로 살펴보자
import glob from 'glob'
glob('data/*.txt',
(err, files) => {
if (err) {
return console.error(err)
}
console.log(`All files found: ${JSON.stringify(files)}`)
})
.on('match', match => console.log(`Match found: ${match}`))
반응형
'Backend > Node.js' 카테고리의 다른 글
비동기 흐름 제어 by Callback (0) | 2025.01.06 |
---|---|
Callback 패턴 (0) | 2025.01.06 |
Module System ( ESM ) (0) | 2025.01.06 |
Module System ( CJS ) (0) | 2025.01.06 |
Node.js Interview (0) | 2024.12.30 |