본문 바로가기

Project Log/CommentTeller

Express 라우터에 async/await

프론트에서 유튜브 비디오 아이디가 넘어오면 백에서 진행되는 단계는 다음과 같다.

 

1. 비디오 아이디로 API 요청 후 댓글 가져오기

2. 특수문자, ㅠㅠ, ㅋㅋ, ㅎㅎ와 같은 한글, 숫자, 한글 이외의 언어 공백으로 변환 

3. 문장에서 단어만 추출하기

4. 최다빈도 단어 추출하기

 

이 네 가지 단계를 각 함수로 분리하고, 순서대로 진행할 수 있도록 async/await 구문을 이용해 프라미스를 return 하도록 만들어 주었다.

그리고 라우터에 요청이 들어오면 저 네가지 함수를 다시 await을 사용해서 순서대로 진행되게 만들어줘야 한다. 그러기 위해서는 라우터 함수에 async를 다는 게 필요했는데, 예를 들면 아래와 같이 단순 무식하게 router함수에 달아보았더니 모양새가 뭔가 안 익숙하기도 하고 이렇게 하는 게 맞는 걸까...? 싶어서 찾아보게 되었다.

 

router.get('/', async(req, res) => {
 const result = await foo();
 res.send(result);
});

 

찾아보니 만약 위에서 호출한 async함수에서 예상치 못한 오류가 난다면, 노드는 Unhandled promise rejection을 출력하게 되고, express는 에러 발생을 인지하지 못해 응답만 지연되게 된다고 한다. 게다가 오류 문구에는 Unhandled promise rejection은 deprecated 될 예정이고 앞으로는 노드가 종료될 것이라고 경고하는 것을 볼 수 있었다.

 

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

 

위에서 작성한 async콜백 함수는 Promise를 리턴해주기 때문에 오류 발생의 경우를 대비해서 바깥에서 catch함수로 처리해주어야 하는데, 바깥에서 catch로 잡아주고 있지 않기 때문에 express가 오류를 잡지 못한다. 즉, 라우팅 핸들러 함수가 reject 된 promise를 처리해줄 필요가 있다는 말!

 

따라서 해결 방법은 express-asyncify나 asyncify-express라는 라이브러리를 이용해서 아예 router를 감싼 다음 깔끔하게 작성하는 방법이 있고 아니면 직접 wrapper함수를 사용하는 방법이 있다. 나는 어차피 플젝이 간단해서 라우터가 하나라 굳이 라이브러리를 쓰지 않고 wrapper함수를 작성한 뒤 사용했다. 

 

1. 라이브러리 사용하기

const express = require('express');
const asyncify = require('express-asyncify');
 
const app = express();
const router = asyncify(express.Router());
 
// ...
 
router.get('/', async (req, res) => {
    const result = await foo();
    res.send(result);
});

 

2. wrapper함수 사용하기

const wrap = asyncFn => {
  return (async (req, res, next) => {
    try {
      return await asyncFn(req, res, next)
    } catch (error) {
      return next(error)
    }
  })  
}

router.get('/', wrap(async (req, res, next) => {
  const result = await foo();
  res.send(result);
}))

 

참고로, koa는 라우트 핸들러에서 async-await지원이 내장되어있다고 한다.