Node.js 인터뷰 질문 31
질문: Node.js에서 오류 우선 콜백 패턴(Error-First Callback Pattern)에 대해 설명해주세요.
답변:
오류 우선 콜백 패턴(Error-First Callback Pattern)은 Node.js에서 비동기 작업의 결과를 처리하는 표준적인 방식입니다. 이 패턴은 Node.js 코어 모듈과 대부분의 NPM 패키지에서 일관되게 사용되며, 비동기 작업의 오류 처리를 효과적으로 할 수 있게 해줍니다.
기본 원칙
오류 우선 콜백 패턴의 기본 원칙은 다음과 같습니다:
- 콜백 함수의 첫 번째 매개변수는 오류 객체를 위해 예약되어 있습니다.
- 오류가 발생하면 첫 번째 매개변수에 오류 객체가 전달됩니다.
- 오류가 발생하지 않으면 첫 번째 매개변수는
null
또는undefined
가 됩니다. - 성공적인 결과 데이터는 두 번째 이상의 매개변수로 전달됩니다.
간단한 예시
// 오류 우선 콜백 패턴을 사용하는 함수 정의
function readFile(path, callback) {
fs.readFile(path, "utf8", (err, data) => {
// 오류가 발생하면 콜백의 첫 번째 매개변수로 전달
if (err) {
return callback(err);
}
// 성공 시 첫 번째 매개변수는 null, 두 번째 매개변수로 데이터 전달
callback(null, data);
});
}
// 함수 사용
readFile("/path/to/file.txt", (err, data) => {
if (err) {
console.error("파일 읽기 오류:", err);
return;
}
console.log("파일 내용:", data);
});
Node.js 코어 모듈 예시
Node.js의 코어 모듈들은 오류 우선 콜백 패턴을 따릅니다. 예를 들어, fs
모듈의 메서드들은 다음과 같이 사용됩니다:
const fs = require("fs");
// 파일 읽기
fs.readFile("/path/to/file.txt", "utf8", (err, data) => {
if (err) {
console.error("파일 읽기 오류:", err);
return;
}
console.log("파일 내용:", data);
});
// 파일 쓰기
fs.writeFile("/path/to/file.txt", "Hello, World!", (err) => {
if (err) {
console.error("파일 쓰기 오류:", err);
return;
}
console.log("파일 쓰기 완료");
});
자신만의 함수에서 오류 우선 콜백 사용하기
사용자 정의 비동기 함수에서도 이 패턴을 적용할 수 있습니다:
function getUserData(userId, callback) {
// 데이터베이스 조회 등의 비동기 작업 가정
if (!userId) {
// 유효하지 않은 입력일 경우 오류 객체 전달
return callback(new Error("유효하지 않은 사용자 ID"));
}
// 비동기 작업 시뮬레이션
setTimeout(() => {
// 사용자 데이터 조회 성공 가정
const userData = {
id: userId,
name: "홍길동",
email: "hong@example.com",
};
// 성공 시 null과 함께 데이터 전달
callback(null, userData);
}, 1000);
}
// 함수 사용
getUserData("user123", (err, user) => {
if (err) {
console.error("사용자 데이터 조회 오류:", err);
return;
}
console.log("사용자 정보:", user);
});
// 오류 상황 테스트
getUserData(null, (err, user) => {
if (err) {
console.error("사용자 데이터 조회 오류:", err);
return;
}
console.log("사용자 정보:", user);
});
오류 우선 콜백 패턴의 장점
- 일관성: Node.js 생태계 전반에 걸쳐 동일한 패턴을 사용함으로써 코드의 일관성을 유지합니다.
- 명확한 오류 처리: 오류 처리 로직이 명확하게 분리되어 있어 가독성이 향상됩니다.
- 흐름 제어: 오류가 발생했을 때 즉시 함수 실행을 중단하고 적절한 오류 처리를 할 수 있습니다.
오류 우선 콜백 패턴의 단점
- 콜백 지옥: 여러 비동기 작업을 순차적으로 처리해야 할 경우 콜백이 중첩되어 '콜백 지옥'이 발생할 수 있습니다.
- 오류 전파 복잡성: 중첩된 콜백에서 오류를 상위 레벨로 전파하는 것이 복잡해질 수 있습니다.
- 코드 가독성 저하: 콜백이 많아질수록 코드의 가독성이 떨어질 수 있습니다.
대안: Promise와 async/await
오류 우선 콜백 패턴의 단점을 극복하기 위해 최신 Node.js에서는 Promise와 async/await를 사용할 수 있습니다:
// Promise 사용
const fs = require("fs").promises;
fs.readFile("/path/to/file.txt", "utf8")
.then((data) => {
console.log("파일 내용:", data);
})
.catch((err) => {
console.error("파일 읽기 오류:", err);
});
// async/await 사용
async function readFileAsync() {
try {
const data = await fs.readFile("/path/to/file.txt", "utf8");
console.log("파일 내용:", data);
} catch (err) {
console.error("파일 읽기 오류:", err);
}
}
readFileAsync();
Promise와 async/await를 사용하면 코드가 더 깔끔해지고 오류 처리도 직관적으로 할 수 있지만, Node.js의 많은 레거시 코드와 라이브러리에서는 여전히 오류 우선 콜백 패턴이 널리 사용되고 있으므로 이 패턴을 이해하는 것이 중요합니다.
콜백 함수를 Promise로 변환하기
기존의 오류 우선 콜백 기반 함수를 Promise로 래핑하여 사용할 수 있습니다:
const fs = require("fs");
// 오류 우선 콜백 함수를 Promise로 래핑
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// Promise 사용
readFilePromise("/path/to/file.txt")
.then((data) => {
console.log("파일 내용:", data);
})
.catch((err) => {
console.error("파일 읽기 오류:", err);
});
// Node.js의 util.promisify 사용
const util = require("util");
const readFile = util.promisify(fs.readFile);
readFile("/path/to/file.txt", "utf8")
.then((data) => {
console.log("파일 내용:", data);
})
.catch((err) => {
console.error("파일 읽기 오류:", err);
});
결론
오류 우선 콜백 패턴은 Node.js에서 비동기 작업의 결과를 처리하는 표준적인 방식입니다. 이 패턴은 일관된 오류 처리 방법을 제공하지만, 복잡한 비동기 흐름에서는 가독성 문제를 일으킬 수 있습니다. 최신 JavaScript의 Promise와 async/await를 사용하면 이러한 문제를 해결할 수 있지만, Node.js 생태계의 많은 부분에서 여전히 오류 우선 콜백 패턴이 사용되므로 이 패턴의 원리와 사용법을 이해하는 것이 중요합니다.