Node.js 인터뷰 질문 93
질문: Node.js의 이벤트 기반 프로그래밍과 이벤트 루프의 동작 방식에 대해 설명해주세요.
답변:
Node.js의 이벤트 기반 프로그래밍은 비동기 작업을 효율적으로 처리하는 핵심 메커니즘입니다. 이벤트 루프와 이벤트 기반 프로그래밍의 구현 방법을 살펴보겠습니다.
1. 이벤트 이미터 구현
// custom-event-emitter.js
class CustomEventEmitter {
constructor() {
this.events = new Map();
}
// 이벤트 리스너 등록
on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
this.events.get(eventName).push(callback);
}
// 이벤트 한 번만 실행
once(eventName, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
// 이벤트 리스너 제거
off(eventName, callback) {
if (!this.events.has(eventName)) return;
if (!callback) {
this.events.delete(eventName);
return;
}
const listeners = this.events.get(eventName);
this.events.set(
eventName,
listeners.filter((listener) => listener !== callback)
);
}
// 이벤트 발생
emit(eventName, ...args) {
if (!this.events.has(eventName)) return;
const listeners = this.events.get(eventName);
listeners.forEach((listener) => {
try {
listener(...args);
} catch (error) {
console.error(`이벤트 처리 중 오류 발생: ${error.message}`);
}
});
}
}
// 사용 예시
class OrderProcessor extends CustomEventEmitter {
constructor() {
super();
this.orders = new Map();
}
processOrder(order) {
// 주문 접수 이벤트 발생
this.emit("orderReceived", order);
// 주문 처리
setTimeout(() => {
this.orders.set(order.id, { ...order, status: "processing" });
this.emit("orderProcessing", order);
// 주문 완료
setTimeout(() => {
this.orders.set(order.id, { ...order, status: "completed" });
this.emit("orderCompleted", order);
}, 2000);
}, 1000);
}
}
const processor = new OrderProcessor();
// 이벤트 리스너 등록
processor.on("orderReceived", (order) => {
console.log(`주문 접수: ${order.id}`);
});
processor.on("orderProcessing", (order) => {
console.log(`주문 처리 중: ${order.id}`);
});
processor.on("orderCompleted", (order) => {
console.log(`주문 완료: ${order.id}`);
});
// 주문 처리 시작
processor.processOrder({ id: "ORDER-123", items: ["item1", "item2"] });
2. 이벤트 루프 이해
// event-loop-example.js
console.log("1. 스크립트 시작");
// 타이머 큐
setTimeout(() => {
console.log("2. 타이머 콜백");
}, 0);
// 프로미스 마이크로태스크
Promise.resolve().then(() => {
console.log("3. 프로미스 콜백");
});
// I/O 큐
const fs = require("fs");
fs.readFile("example.txt", "utf8", (err, data) => {
if (err) {
console.error("4. 파일 읽기 오류:", err);
return;
}
console.log("4. 파일 내용:", data);
});
// setImmediate 큐
setImmediate(() => {
console.log("5. immediate 콜백");
});
// process.nextTick 마이크로태스크
process.nextTick(() => {
console.log("6. nextTick 콜백");
});
console.log("7. 스크립트 종료");
// 출력 순서:
// 1. 스크립트 시작
// 7. 스크립트 종료
// 6. nextTick 콜백
// 3. 프로미스 콜백
// 2. 타이머 콜백
// 5. immediate 콜백
// 4. 파일 내용: ...
3. 비동기 이벤트 처리
// async-event-handler.js
class AsyncEventEmitter extends CustomEventEmitter {
// 비동기 이벤트 리스너 등록
async onAsync(eventName, callback) {
this.on(eventName, async (...args) => {
try {
await callback(...args);
} catch (error) {
this.emit("error", error);
}
});
}
// 비동기 이벤트 발생
async emitAsync(eventName, ...args) {
if (!this.events.has(eventName)) return;
const listeners = this.events.get(eventName);
await Promise.all(
listeners.map((listener) => Promise.resolve(listener(...args)))
);
}
}
// 사용 예시
class DataProcessor extends AsyncEventEmitter {
async processData(data) {
// 데이터 처리 시작 이벤트
await this.emitAsync("processingStart", data);
// 데이터 처리
const processedData = await this.transform(data);
// 데이터 처리 완료 이벤트
await this.emitAsync("processingComplete", processedData);
return processedData;
}
async transform(data) {
// 데이터 변환 로직
return new Promise((resolve) => {
setTimeout(() => {
resolve(data.map((item) => item * 2));
}, 1000);
});
}
}
const processor = new DataProcessor();
// 비동기 이벤트 리스너 등록
processor.onAsync("processingStart", async (data) => {
console.log("데이터 처리 시작:", data);
await someAsyncOperation();
});
processor.onAsync("processingComplete", async (data) => {
console.log("데이터 처리 완료:", data);
await saveToDatabase(data);
});
// 에러 처리
processor.on("error", (error) => {
console.error("처리 중 오류 발생:", error);
});
4. 이벤트 기반 스트림 처리
// event-stream.js
const { Transform } = require("stream");
class DataTransformStream extends Transform {
constructor(options = {}) {
super({ ...options, objectMode: true });
this.count = 0;
}
_transform(chunk, encoding, callback) {
try {
// 데이터 변환 처리
const transformed = this.processChunk(chunk);
this.count++;
// 진행 상황 이벤트 발생
this.emit("progress", {
processed: this.count,
data: transformed,
});
callback(null, transformed);
} catch (error) {
callback(error);
}
}
_flush(callback) {
// 스트림 종료 시 처리
this.emit("summary", {
totalProcessed: this.count,
});
callback();
}
processChunk(chunk) {
// 실제 데이터 처리 로직
return typeof chunk === "object" ? { ...chunk, processed: true } : chunk;
}
}
// 사용 예시
const transformStream = new DataTransformStream();
transformStream.on("progress", ({ processed, data }) => {
console.log(`처리된 항목: ${processed}`, data);
});
transformStream.on("summary", ({ totalProcessed }) => {
console.log(`총 처리된 항목: ${totalProcessed}`);
});
transformStream.on("error", (error) => {
console.error("스트림 처리 중 오류:", error);
});
// 데이터 스트리밍
const data = [
{ id: 1, value: "a" },
{ id: 2, value: "b" },
{ id: 3, value: "c" },
];
data.forEach((item) => transformStream.write(item));
transformStream.end();
요약
Node.js 이벤트 기반 프로그래밍의 주요 특징:
이벤트 이미터
- 이벤트 등록과 발생
- 이벤트 리스너 관리
- 에러 처리
이벤트 루프
- 페이즈 이해
- 실행 순서
- 마이크로태스크
비동기 이벤트
- Promise 통합
- 비동기 리스너
- 병렬 처리
스트림 이벤트
- 데이터 스트리밍
- 진행 상황 모니터링
- 에러 처리
성능 고려사항
- 메모리 관리
- 이벤트 리스너 제한
- 에러 전파