TIL 211126 | 에러 처리 (동기, 비동기)

서버 어플리케이션에서 에러를 잘 처리하는 것은 굉정히 중요함.
서버는 적게는 수십, 많게는 수천 수만명의 사람들이 동시에 이용하는 것이기 때문에 적절한 에러처리를 하지 못했을 경우에는 많은 사람들이 서버를 이용하지 못하는 심각한 상황이 발생 할 수 있다.

에러 처리를 잘 한다는건 무슨 의미일까?

  1. 클라이언트가 요청한 request를 제대로 처리하지 못한 경우, 적절한 에러메세지를 보내주어 클라이언트에게 에러에 대한 메세지를 전달 할 것.

  2. 시스템 내부적으로 큰 문제가 발생하였어도, 서버가 죽지 않도록 문제 상황에서 빠르게 복귀 될 수 있도록 예외 처리를 잘 하는 것.

동기적인 경우 에러 처리

readFileSync는 동기적 API이다.
동기적이란 호출된 코드가 완전히 다 처리가 되어야 다음으로 넘어 갈 수 있다.
예를 들어 const data = fs.readFileSync('/file1.txt'); 코드가 다 끝나야 다음 코드를 수행 할 수 있는 것이다.
만약 저 코드에서 에러가 발생하면 에러가 던져지기 때문에 try, catch로 에러를 잡아 낼 수 있다.
try - catch로 잡지 않더라도 마지막 안전망인 에러처리 미들웨어에 포착되어 에러를 처리 할 수 있다.

app.get('/file1', (req, res) => {
   try{
     const data = fs.readFileSync('/file1.txt');
   } catch (error) {
     res.status(404).send('File not found')
   }
  )}
  .
  .
  .
  // 에러처리 미들웨어.
  app.use((error, req, res, next) => {
  console.error(error);
  res.status(500).json({ message: 'Something went wrong' });
});

비동기적인 경우 에러 처리

1. 비동기적 API를 쓰는 경우

readFile은 비동기적 API이다.
비동기적인 것은 코드가 완료될 때까지 기다리는 것이 아닌, 호출만 해놓고 넘어가는 가는데 이러한 경우에는 내부적으로 에러가 발생한다. 즉 에러가 외부로 던져지지 않는다는 뜻.
때문에 try-catch로 잡아도 에러가 잡히지 않는다.
또한 마지막 안전망인 에러처리 미들웨어에도 포착되지 않는다.
비동기적인 경우 에러를 처리하기 가장 적합한 정소는 콜백함수 내부이다.
때문에 비동기적 함수를 이용할 때에는 콜백함수 내에서 적절한 에러처리를 해주어야 한다.
콜백함수에서 err을 인자로 받았기 때문에 if를 이용해서 에러가 발생한 경우에 대해 적절히 처리해 주어야 한다.

app.get('/file1', (req, res) => {
 fs.readFile('/file1.txt', (err, data) => {
   if(err) {
     res.status(404).send('File not found')
   }
 });
});
 .
 .
 .
 // 에러처리 미들웨어.
 app.use((error, req, res, next) => {
 console.error(error);
 res.status(500).json({ message: 'Something went wrong' });
});

2. promise를 사용하는 경우

promise도 대표적인 비동기이다.
promise는 콜백함수를 등록하지는 않지만 then과 catch를 통해서 처리한다.

app.get('/file2', (req, res) => {
 fsAsync
   .readFile('/file2.txt')
   .then((data) => {})
   .catch((error) => {
     res.status(404).send('File not found')
   });
});

3. async-await을 사용하는 경우

함수 앞에 async를 붙여주면 함수 내부에서는 await 키워드를 이용해서 동기적으로 처리되는 것처럼(순차적) 할 수 있지만, 함수 자체는 promise로 감싸 진다. 때문에 함수에서 에러가 발생하면 promise에서 발생하는것과 같이 처리해 준다.
프로미스 내부에서 발생하는 에러는 프로미스 안에서 발생하는 에러이므로 catch를 이용해서만 잡을 수 있다.

app.get('/file3', async (req, res) => {
 try {
   const data = await fsAsync.readFile('/file2.txt'); // 이 코드는 동기적
   // 파일을 다 읽을때까지 기다렸다가 파일이 읽어지면 data에 할당이 된다.
   //하지만 이 코드에서 에러가 발생했을 시 안전망에 포착되지 않는다.
 } catch {
   res.status(404).send('File not found')
 }
});

그러나, 비동기일때도 에러처리 미들웨어 즉 마지막 안전망에 포착될 수 있게 할 순 없을까?

방법은 있다!!

npm i express-async-errors 를 통해 라이브러리를 설치해준다,.

그 후에 import를 해주고 사용하면 된다.

import {} from 'express-async-errors

미들웨어에서 프로미스를 return하는 경우에만 에러를 마지막 안전망에서 포착해 낼 수 있다.

꼭 미들웨어 안에서 프로미스를 사용한다면 return을 해주어야 한다!
async-await은 자동으로 리턴하기 때문에 return 안써줘도 됨!
리턴만 해주게 되면 마지막 에러처리 미들웨어에서 에러를 포착해 낼 수 있다.

app.get('/file2', (req, res) => {
  return fsAsync.readFile('/file2.txt')
});

app.get('/file3', async (req, res) => {
    const data = await fsAsync.readFile('/file2.txt'); 
});

하지만 이렇게 마지막 안전망에 걸리게 하는거 보다는, 에러가 발생했을 시 적절한 에러메세지를 사용자 전달해 주기 위한 에러처리를 하는것이 더욱 좋은 방식이다.

좋은 웹페이지 즐겨찾기