Node.js 에러 핸들링

Pien·2022년 9월 17일
0

위코드

목록 보기
9/10
post-thumbnail

에러가 없는 프로그램은 없다. 에러의 원인 으로는 오타, 문법 실수, 네트워크 오류, DB 오류 등 셀수 없을 정도로 많다.
모름지기 개발자라면 에러의 발생을 예상해 컨트롤 함으로써 서버가 중단되는 사태를 막아야한다.

에러 핸들링의 두가지 종류

에러 핸들링은 에러 핸들링과 예외 핸들링 두가지 종류로 구분 된다.
에러는 코드가 작동하면서 자체적으로 나는 오타, 네트워크 에러 등을 말하고, 예외는 개발자가 특정 조건을 걸어 의도적으로 에러를 발생시키는 것을 말한다.

에러 핸들링

에러 핸들링은 일련의 오류로 인해 코드가 더이상 진행할 수 없을 때 엔진 자체에서 에러를 발생하는 경우, 해당 에러를 컨트롤을 하는 것이다.

문법, 네트워크 에러와 같은 오류가 발생하면, Node.js 는 서버를 중단 시킨다.
서버 중단과 같은 크리티컬한 이슈가 발생하는 사태가 일어나지 않기 위해 개발자들은 에러 핸들링을 통해 서버가 계속 작동 하도록 코드를 작성 한다.
프로그램에서 발생한 에러는 주로 try..catch 문을 통해 에러 핸들링을 한다.

try...catch

try {
// 코드
} catch(err) {
// 에러 핸들링
}

try..catch 문은 try 문안의 코드가 실행되는 동안, 에러가 발생된 경우, 코드를 중지하고 catch문으로 넘어간다.
catch 문으로 넘어 오게 되면, 매개변수에 에러에 대한 내용을 객체로 담은 뒤, catch 문에 있는 코드를 실행시켜, 서버의 중단을 막는다.

try..catch 문은 유효하지 않은 코드로 인해 발생하는 에러와 비동기로 작동되는 코드의 에러는 잡지 못한다.
비동기로 작동하는 코드는 try 문을 벗어난 뒤 에러가 발생하기 때문에 에러를 잡지 못하는 것이다.
node.jsexpress 모듈은 코드가 비동기적으로 작동 하기 때문에 우리는 에러 핸들링을 위해, 비동기 코드를 동기적으로 바꿀 필요가 있다.
이 때 사용하는 것이 async..await 문 이다.

async..await

async..await 문은 동기적 코드를 비동기 코드로 만들고 await 문이 붙은 곳을 동기적으로 코드를 실행시켜 준다.

node.js express 환경에서 HTTP 통신은 기본적으로 비동기로 코드가 실행된다고 했다. 그래서 우리는 async..await 문을 사용해 HTTP 통신을 동기적 코드로 만들어 실행 할 경우, try..catch 문을 통해 에러 핸들링이 가능해 진다.

에러 핸들링 코드

//Controller
const postUp = async (req, res) => {
  try{
        const { title, content } = req.body;
        const userId = req.userId;
        if ( !title || !content ) {
            res.status(400).json({message: "title,content 값은 필수 입니다"})
        }
        await postService.postUp( title, content, userId );
        res.status(201).json({ message:"POSINGUP_SUCCESS"});
  } catch(err) {
    console.log(err);
    res.status(statusCode || 500).json({message:err.message})
  }
});

위의 코드를 보면 try 문에서 코드가 실행되고, catch 문은 에러의 코드와 에러 메시지를 프론트 단에 출력 해준다.
try..catch 문을 통한 에러 핸들링은 주로 컨트롤러 레이어에서 실행하고, 처리 한다. 이는 컨트롤러 하위 레이어 에서 실행되는 모든 에러는 컨트롤러 레이어 에서 처리함을 의미한다.
서비스 레이어나 다른곳에서 하지 않고, 컨트롤러 레이어에서 에러 핸들링을 처리 하는것은, 에러 핸들링을 한 곳에서 처리하는게 효율적이고 코드의 가독성도 높아지기 때문이다.

예외적 핸들링

예외 핸들링은 개발자가 의도적으로 에러를 발생시켜 코드를 중지 시키고, 프론트 단에 에러 메시지를 출력해 어떠한 로직에서 에러가 발생했는지 알 수 있게 하는 것이다.

예외 핸들링은 자바스크립트가 에러를 내보내는것이 아니기 때문에 try..catch 문은 사용하지 않는다.
대신 if 문과 같이 특정 조건을 걸어 조건에 만족한 경우 에러를 생성하고 에러 메시지와 상태코드를 추가 한 뒤, 에러를 throw 해 최종적으로 컨트롤러 레이어에서 에러 처리를 하게 된다.

예외 핸들링 예시, throw

//Service
const tweetLike = async (user_id, tweet_id) => {
  const find = await likeDao.findLike(user_id, tweet_id)
  if (find) {
    const err = new Error("이미 좋아요된 게시글 입니다.")
    err.statusCode = 409;
    throw err
  }
    const tweetLike = await likeDao.tweetLike(user_id,tweet_id);
    return tweetLike;
  };

throw 는 에러를 반환하기 위해 존재한다.
반환 한다는 목적으로는 return 문과 동일하지만 에러를 반환하기 위해 만들어 졌다고 볼 수 있다.

미들 웨어를 통한 에러 핸들링

//Controller.js
const postUp = asyncWrap(async (req, res) => {
    const { title, content } = req.body;
    const userId = req.userId;
    if ( !title || !content ) {
        res.status(400).json({message: "title,content 값은 필수 입니다"})
    }
    await postService.postUp( title, content, userId );
    res.status(201).json({ message:"POSINGUP_SUCCESS"});
}
//------
//asyncWrap.js
function asyncWrap(asyncController) {
    return async (req, res, next) => {
        try {
            await asyncController(req, res)
        }
        catch(error) {
            next(error);
        }
    };
}
//------
//app.js
app.use(routes);
app.use((err, req, res, next)=> {
  console.log(err)
  res.status(err.statusCode || 500).json({ message: err.message });
})

상단의 에러 핸들링 코드를 보면 컨트롤러 레이어에서 try..catch 문이 사라지고 asyncWrap 함수를 씌워놨다.
컨트롤러 레이어에서 코드가 실행될 경우 asyncWrap 함수 안에서 코드가 실행 된다. asyncWrap 함수는 try..catch 문으로 감싸져 있어 컨트롤러 레이어에서 실행되는 코드는 결국 try..catch 문으로 에러가 검출 되는 로직과 같다.
asyncWrap 에서 에러를 잡으면 에러를 가지고 다음 미들웨어로 가져 가는데, app.js를 보면 routes 미들웨어 다음에 에러처리 미들웨어가 존재하며, 최종적으로 에러메시지와 상태 코드를 프론트 단으로 출력 해 준다.
위 처럼 미들웨어를 통한 에러 핸들링을 할 경우, 컨트롤러 단에서 로직마다 try..catch 문을 사용하지 않아 코드가 깔끔하고 에러 처리에 대한 처리를 한곳에서 맡아서 하기 때문에 코드 유지보수와 가독성이 우수해진다.


마치며

시스템적 에러 핸들링은 결국, try..catch 문과 async-await 문을 잘 사용할 경우, 컨트롤 하기 어렵지 않다.
하지만 값이 잘못 들어온 경우, 개발자가 예외적 핸들링을 통해 시스템적 에러가 발생 하기 전에 처리를 할 수 있도록 하는 기술이 필요 할 것 이다.
이번에 API를 작성하며 DB에 값을 넣었을 때 에러가 발생할 값일 경우 서비스 레이어에서 예외적 핸들링을 통해 에러 핸들링을 진행해 보았는데, 매우 만족스러운 경험 이었다.

0개의 댓글