
Javascript는 개발자가 다양한 방법으로 오류를 발생시킬 수 있습니다.
일부 개발자들은 오류를 발생시킬 때 문자열을 사용하고 커스텀 타입을 정의합니다.
하지만 Node.js 내장 Error 객체를 사용한다면
내 코드와 제 3자 라이브러리의 균일성을 유지할 수 있고, 스택정보와 같은 중요한 정보도 보존할 수 있습니다.
예외가 발생할 때는,
오류 이름이나 관련 HTTP 오류 코드같은 상황별 추가적인 속성으로 채우는 것이 좋은 방법입니다.
1. ❌ 좋지 않은 예시
// 문자열을 던질 경우, 스택정보나 다른 중요한 데이터 정보를 알 수 없습니다.
if(!productToAdd)
throw ("How can I add new product when no value provided?");
2. ⭕ 좋은 예시
// 일반적 기능의 함수에서의 에러 처리
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
// EventEmitter 에러 처리 방법
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
// Promise 에러 처리 방법
const addProduct = async (productToAdd) => {
try {
const existingProduct = await DAL.getProduct(productToAdd.id);
if (existingProduct !== null) {
throw new Error("Product already exists!");
}
} catch (err) {
// ...
}
}
2. ⭕⭕ 더 좋은 예시
// Node의 에러를 처리하는 중앙화 객체
function AppError(name, httpCode, description, isOperational) {
Error.call(this);
Error.captureStackTrace(this);
this.name = name;
//...other properties assigned here
};
AppError.prototype = Object.create(Error.prototype);
AppError.prototype.constructor = AppError;
module.exports.AppError = AppError;
// 예시 - 사용자 예외 처리
if(user == null)
throw new AppError(commonErrors.resourceNotFound, commonHTTPErrors.notFound, "further explanation", true)
동작상 에러는 API에서 잘못된 입력을 받는 등의 해당 에러의 영향을 완전히 이해할 수 있고, 신중하게 처리할 수 있는 경우를 의미합니다.
프로그래머 에러는 정의되지 않는 변수를 읽는 등, 어플리케이션을 안정적으로 다시 시작하게 만드는 알 수 없는 코드 에러를 의미합니다.
에러가 날 때마다 어플리케이션을 다시 시작할 수 있지만,
충분히 핸들링할 수 있는 에러를 가지고 사용자를 다운시키는 것은 좋지 않습니다.
반대로, 알 수 없는 프로그래머 에러를 두고 어플리케이션을 그대로 두는 것은 예측 불가능한 반응을 불러올 수 있습니다.
따라서 위 두 가지 오류를 구별해 요령있는 처신과 상황에 따른 균혀잡힌 접근을 가능하게 해야합니다.
동작상의 에러를 표시하는 방법은 위 에러 처리 예시와 같습니다.
위 예시에 나온 것처럼, 한 곳에서 집중적으로 에러를 처리하는 것이 좋습니다.
에러 처리를 위한 전용 객체가 없을 경우 잘못된 처리로 인해 중요한 에러를 놓칠 가능성이 커집니다.
따라서 아래와 같은 흐름으로 에러를 처리하는 것이 좋습니다.
일부 모듈이 에러를 던짐 ➡ API 라우터가 에러를 잡음
➡ 에러 검출을 담당하는 미들웨어에 전달 ➡ 에러 신뢰 여부를 알리고 중앙 에러 처리기 호출
미들웨어에서 에러 신뢰여부를 판단하기 때문에 앱을 정상적으로 재시작 할 수 있습니다.
Express 미들웨어 내에서 직접 오류를 처리하는 것이 일반적이지만 잘못된 관습입니다.
미들웨어 내에서 처리했을 경우 웹 이외의 인터페이스에 발생하는 에러를 해결할 수 없습니다.
에러 처리 흐름 예시
// DAL 계층, 이곳에서 에러를 다루지 않습니다.
DB.addDocument(newCustomer, (error, result) => {
if (error)
throw new Error("Great error explanation comes here", other useful parameters)
});
// API route 코드, 비동기&동기 오류를 모두 잡아 미들웨어에 넘깁니다.
try {
customerService.addNew(req.body).then((result) => {
res.status(200).json(result);
}).catch((error) => {
next(error)
});
}
catch (error) {
next(error);
}
// 에러 핸들링 미들웨어, 에러 처리를 '중앙 집중식 오류 처리기(Centralized error handler)'에 위임합니다.
app.use(async (err, req, res, next) => {
const isOperationalError = await errorHandler.handleError(err);
if (!isOperationalError) {
next(err);
}
});
// 중앙 집중식 오류 처리기(Centralized error handler)
module.exports.handler = new errorHandler();
function errorHandler() {
this.handleError = async function(err) {
await logger.logError(err);
await sendMailToAdminIfCritical;
await saveInOpsQueueIfCritical;
await determineIfOperationalError;
};
}