async 함수를 쓸 때 습관적으로 무조건 try-catch 문을 쓰는 사람들이 많이 있다.
하지만 async
에서 try-catch
로 전체 코드를 묶고 catch
문에서 throw e
하는 것과 try-catch
문을 아예 쓰지 않는것은 흐름상으로나 결과적으로나 완전히 같은 결과를 만들어낸다.
async 함수를 쓸 때 습관적으로 무조건 try-catch 문을 쓰는 사람들이 많이 있다.
const func = async () => {
try {
const value = await someFunc();
const result = await anotherFunc();
return result;
} catch (err) {
throw err;
}
}
이런 식으로 async 함수에서 발생하는 비동기 에러를 다시 전달하기 위해 try-catch 로 묶고, catch 에서 에러를 다시 던지는 코드를 많이 발견할 수 있다.
하지만 만약 try-catch 문 없이 그냥 async 함수를 쓴다면 무슨 차이가 생길까?
const makeError = async () => { throw new Error('에러 클래스에 의한 에러') }
const withTryCatch = async () => {
try {
console.log('try-cath 를 사용한 async')
const result = await makeError();
console.log('withTryCatch - 에러가 발생하는 위치 아래에 있는 코드 (실행되면 안됨)');
return result;
} catch (err) {
throw err;
}
}
const withoutTryCatch = async () => {
console.log('try-cath 없는 async')
const result = await makeError();
console.log('withoutTryCatch - 에러가 발생하는 위치 아래에 있는 코드 (실행되면 안됨)');
return result;
}
withTryCatch()
.then(res => {
console.log('withTryCatch - 성공결과', res)
}).catch(err => {
console.log('withTryCatch - 실패결과', err.message)
});
withoutTryCatch()
.then(res => {
console.log('withoutTryCatch - 성공결과', res)
})
.catch(err => {
console.log('withoutTryCatch - 실패결과', err.message)
})
withTryCatch
와 withoutTryCatch
는 둘다 async 함수이다.
단 withTryCatch
는 사람들이 많이 쓰는것처럼 try-catch 문으로 함수 안쪽 코드를 묶고 catch 문에서 받은 에러를 던졌고, withoutTryCatch
는 그냥 작성했다.
'try-cath 를 사용한 async'
Promise { <pending> }
'try-cath 없는 async'
Promise { <pending> }
'withTryCatch - 실패결과' '에러 클래스에 의한 에러'
'withoutTryCatch - 실패결과' '에러 클래스에 의한 에러'
try-catch
를 쓰든 안쓰든 상관없이
Promise.reject
처리되어 상위 컨텍스트에서 비동기 에러로 처리된다.상위 컨텍스트로 에러를 전파하기 위해 async
함수의 내부를 try-catch
로 묶을 필요는 없다.
즉, 상위 컨텍스트로 에러를 전파하기 위한 try-catch 문은 필요없는 코드인 것이다.
NodeJS 에서 비동기 함수를 작성할 때 대부분의 경우 try catch 문을 작성하지 않는게 오히려 바람직하다.
다만 유일하게 try-catch를 꾸준히 써야 하는 부분이 있는데 바로 컨트롤러 레이어 로직이다.
비동기 코드가 성공하든 에러가 발생하든 결국 클라이언트에게는 정상적인 응답을 내주어야 한다. 그 응답이 해당 요청을 수행할 수 없다는 에러이더라도 말이다.
때문에, 서비스로직까지는 비동기에러를 계속 try-catch 없이 그대로 전파해 주는게 바람직하고,
컨트롤러에선 try-catch를 통해 더 이상 에러가 전파되는것을 차단하고 에러내용을 정리해 400~500번대 상태코드와 함께 응답을 해주는 것이다.
ExpressJS 의 경우 이 컨트롤러 레이어는 미들웨어 함수에 해당한다.
app.get('/foo', async (req, res, next) => {
try {
// 컨트롤러 로직
// 비동기 결과가 reject 라면 catch 문으로 점프
const some = await something();
// 비동기 결과가 reject 라면 catch 문으로 점프.
const another = await anotherThing();
if (another.result > 30) {
// Promise.reject를 리턴해도 async 함수에선 에러가 던져진것과 동등한것으로 간주된다.
return Promise.reject({
message: '뭔가 비즈니스적으로 30을 초과하면 안되는 그런 것',
status: 403,
})
}
// 앞쪽에서 아무런 문제도 없어야 성공결과가 응답된다.
res.json(another);
} catch (err) {
// 여기에선 따로 throw err를 하지 않는다.
// 상위 컨텍스트로 에러를 전파하는게 아니라 클라이언트로 에러를 응답해야 하는 레이어이다.
res.status(err.status || 500).json({
message: err.message || 'unknown error'
})
}
});
좋은 글 잘보고 갑니다.