에러가 발생할 경우 어떻게 공통로직으로 처리를 해주어야 하는가?
express를 이용하면 미들웨어를 app.use()
를 사용하여 쉽게 등록할 수 있다. 이를 이용하면 에러를 처리하는 미들웨어도 쉽게 사용가능하다.
Error handling 미들웨어 선언하기
기본적으로 express에서 오류를 처리하는 공통 미들웨어를 작성할 때 다음과 같이 해주어야 한다.
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send("Something broke!");
});
인자 3개를 선언하게 되면 일반적인 미들웨어이고 error 미들웨어는 인자 4개를 선언해야 한다.
express-generator로 만들어보면 이렇게 만들어져 있다.
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
주의해야 할 점은 express-generator로 만들었을 경우 저 에러 핸들링하는 코드를 맨 아래로 내려야 한다는 것이다.
async await 사용하기
Express는 ES6가 나오기 이전에 거의 다 만들어졌기 때문에 비동기 에러를 처리하는데 잘 작동하지 않는다.
Promise reject시 에러 처리 미들웨어에서 사용이 되지 않는 것을 볼 수 있다.
app.get("*", function (req, res) {
return new Promise((resolve, reject) => {
setImmediate(() => reject(new Error("에러 발생")));
});
});
app.use(function (error, req, res, next) {
console.log("호출 되지 않음!");
res.json({ message: error.message });
});
(node:6852) UnhandledPromiseRejectionWarning: Error: 에러 발생
at Immediate.<anonymous> (/Users/gim-yohan/Desktop/Projects/nodejs/mysessionApp/app.js:34:20)
at processImmediate (internal/timers.js:456:21)
(node:6852) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
헬퍼 함수 사용하여 비동기 오류 처리하기
function wrapAsync(fn) {
return function (req, res, next) {
// 모든 오류를 .catch() 처리하고 next()로 전달하기
fn(req, res, next).catch(next);
};
}
헬퍼 함수로 wrapAsync를 커스텀으로 만들면 된다. 한줄로 쓸 수도 있다.
const wrapAsync = (fn) => (req, res, next) => fn(req, res, next).catch(next);
app.get(
"*",
wrapAsync(async function (req, res) {
await new Promise((resolve) => setTimeout(() => resolve(), 50));
// 비동기 에러
throw new Error("에러 발생!");
})
);
app.use(function (error, req, res, next) {
res.json({ message: error.message });
});
app.listen(3000);
function wrapAsync(fn) {
return function (req, res, next) {
// 모든 오류를 .catch() 처리하고 체인의 next() 미들웨어에 전달
// (이 경우에는 오류 처리기)
fn(req, res, next).catch(next);
};
}
정말 기가 막힌 해결방법이다!
각각 라우터에서 독립적으로 해결하지 않고 모든 오류를 하나의 함수로 만들어서 처리하게 된다면 코드가 깔끔해 질 것이다.
DB에서 오류가 발생하거나 한다면 각각 라우터에서 error 캐칭을 안해도 된다. 이걸 보고 무릎을 탁치며 얼마 전 끝난 프로젝트를 떠올렸다.
const connection = require("../db/dbconnect");
// CREATE
const doInsertQuery = (query, data = []) => {
return new Promise((resolve, reject) => {
connection.execute(query, data, (err, results, fields) => {
if (err) reject(err);
resolve(results.insertId); // 입력된 데이터 idx;
});
});
};
// READ
const doSelectQuery = (query, data = []) => {
return new Promise((resolve, reject) => {
connection.execute(query, data, (err, results, fields) => {
if (err) reject(err);
resolve(results);
});
});
};
얼마 전 프로젝트에서 사용했던 방법이다.
DB에서 오류가 발생할 경우 방도가 없어 if(err)
이 공통으로 들어가게끔 처리하곤 했는데 그럴 필요가 없는 것이다.
이 방법을 미리 알았더라면 좋았을 것이다. 이래서 사람은 공부를 해야 한다.