Express 라우트에서 Async Await를 사용하려면?
Node.js 7.6 버전 이후에 도입된 async — await는 callback 혹은 Promise 중첩이 불러오는 코드의 지저분함과 핸들링의 어려움을 많이 제거해주었습니다. async-await는 기본적으로 비동기 코드를 하나의 흐름이 있는 것 처럼 만들어 주기 때문에 읽기 좋은 코드를 작성할 수 있습니다. 당연히 코드의 흐름제어 난이도가 낮아지는건 두말 할 필요가 없습니다. 그렇다면, express에서 어떻게 async — await를 사용하면 좋을까요?
Express 라우트에 async — await 사용하기
테스트할 비동기 코드는 아래와 같습니다.
const asyncFunction = () => {
return new Promise(resolve => {
setTimeout(_ => {
resolve({ message: 'success' })
}, 3000);
})
}
비동기 코드를 테스트할 API 구현 내용입니다.
app.use('/async-function', async(req, res) => {
const result = await asyncFunction()
res.json(result)
})
정상적인 경우에 async, await는 잘 작동합니다. 그렇다면 에러가 발생한 경우 어떻게 될까요?
Express 라우트는 async 메소드의 에러를 감지하지 못한다?
async 메소드가 아닌 경우 Error가 발생하면 express의 에러 핸들러에서 처리합니다.
app.use('/async-function', (req, res) => {
throw new Error('사용자 정의 에러 발생')
})
만약 async 메소드라면 에러는 어떻게 될까요?
app.use('/async-function', async (req, res) => {
throw new Error('Async 사용자 정의 에러 발생')
})
위 경우와 다르게 UnhandledPromiseRejectionWarning과 오류와 함께 나중에는 이 경우 앱이 종료될 수 있다는 경고를 출력합니다. 만약 이렇게 처리가 되지 않은 Promise reject때문에 앱이 종료되면 매우 위험해집니다.
try-catch로 에러가 발생하면 express 라우터의 next
메소드로 넘기면 되지 않을까요?
try-catch 혹은 직접 async wrapper 만들기
app.use('/async-function', async (req, res, next) => {
try {
const result = await asyncFunction()
throw new Error('Async 사용자 정의 에러 발생')
res.json(result)
} catch (error) {
next(error)
}
})
위 메소드처럼 async 메소드에서 에러가 발생하면 try-catch 안에서 next
메소드에 에러 객체를 전달해 express가 처리할 수 있도록 합니다. 이제는 async-awiat를 라우트 내부에서 사용할 때마다 사용해야하는 문제가 있습니다.
불필요한, 반복적인 에러 핸들링 코드를 줄이려면 async-await를 대신 수행해주는 래퍼 메소드가 있으면 됩니다.
const wrap = asyncFn => {
// FIXME: Promise와 catch를 이용하면 더 간결해질 것 같습니다.
return (async (req, res, next) => {
try {
return await asyncFn(req, res, next)
} catch (error) {
return next(error)
}
})
}
wrap 메소드는 앞으로 async-await가 필요한 라우트 핸들러에 사용합니다. 아래와 같이 사용할 수 있습니다.
app.use('/async-function', wrap(async (req, res, next) => {
// await가 필요한 작업을 합니다.
}))
try-catch를 이용해 상위 래퍼 메소드에서 에러 핸들링을 해주기 때문에 모든 메소드에 try-catch를 사용할 필요가 없어졌습니다. async — await가 필요한 부분에만 wrap 메소드를 사용하면 됩니다.
방금까지 진행한 작업들을 대신 해주는 라이브러리들이 꽤 많이 있습니다. 내부 구현은 비슷합니다. 위 wrap
메소드의 Promise판은 https://github.com/Abazhenov/express-async-handler 에서 보실 수 있습니다.
koa같은 웹 프레임워크는 라우트 핸들러에서 async-await 지원이 내장되어있습니다. 때문에 위 같은 작업을 하지 않아도 됩니다. 하지만 단순히 async-await 때문에 express를 사용하지 않고 다른 프레임워크를 선택하기에는 express가 기본적으로 제공하는 기능들이 괜찮다고 생각합니다.