.Catch()의 Return - 자바스크립트 고수만 쓴다는 비동기 에러 처리 기술
JavaScript에서 비동기 함수 에러 처리에는 여러가지 방법이 있다. 이 중 Promise의 .catch 에서 return을 값을 잘 준다면 코드의 가독성을 높일 수 있다.
약간의 수정 만으로 가독성을 높이는 방법, Early Return도 알아보세요!
.then과 .catch만을 쓰기
const data = await fetch('/api/something')
.then((res) => res.json())
.catch((err) => {
console.log("Error: " + err.message)
})
console.log(data)
자 이 코드에서 fetch()
나 res.json()
함수가 에러를 반환한다면 어떻게 될까?
물론 .catch()
가 오류들을 잡아 console.log
를 할 것이다. 하지만 그 다음 console.log(data)
도 호출되게 된다.
우리는 오류가 발생하면 Error만 출력되고 data는 뜨지 않도록 하고 싶다. 어떻게 할 수 있을까?
const data = await fetch('/api/something')
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => {
console.log("Error: " + err.message)
})
물론 이렇게도 할 수 있다. 하지만 가독성이 썩 좋아보이지는 않는다.
게다가 이제 악조건을 하나 더 걸어보자. fetch()
나 res.json()
의 오류 말고도 /api/something
API의 오류도 잡아야 한다고 가정해보자:
{
"success": false,
"message": "Oh no!"
}
이제는 API가 보내준 success
가 true, false 인지도 확인해야 한다. 괭장히 야매로 짠다면 다음과 같이 짤 수 있다.
const data = await fetch('/api/something')
.then((res) => res.json())
.then((data) => {
if (!data.success) {
console.log("Error: " + data.message)
return
}
console.log(data)
})
.catch((err) => {
console.log("Error: " + err.message)
})
if문을 추가하였기 때문에 상당히 가독성이 떨어진 것을 볼 수 있다. 어떻게 하면 개선할 수 있을까?
Await+Try Catch를 쓰기
이런 상황에서 많은 개발자들은 다음과 같이 Try Catch 문법을 사용하려고 시도한다:
try {
const data = await fetch('/api/something')
.then((res) => res.json())
if (!data.success) {
console.log("Error: " + data.error)
return
}
console.log(data)
} catch (err) {
console.log("Error: " + err.message)
}
전 보다 코드가 깔끔해졌고 코드 순환 방향이 명확해 졌다. 하지만 여전히 코드 흐름을 깨는 try catch문은 가독성에 부정적인 영향을 준다.
.catch에게 Return을 주기
const data = await fetch('/api/something')
.then((res) => res.json())
.catch((err) => {
return {
success: false,
message: err.message
}
})
if (!data.success) {
console.log("Error: " + data.message)
return
}
console.log(data)
이런 식으로 .catch 문에 return을 준다면 만약 위에 fetch()
, res.json()
에 문제가 발생할때 data를 { success: false, message: err.message }
로 변경하도록 만들 수 있다.
화살표 함수는 return을 생략할 수 있으므로 더 줄이면 다음과 같다:
const data = await fetch('/api/something')
.then((res) => res.json())
.catch((err) => ({
success: false,
message: err.message
}))
if (!data.success) {
console.log("Error: " + data.message)
return
}
console.log(data)
한눈에 읽기 쉽고 흐름이 명확한 가독성이 높은 코드를 작성할 수 있다.
Promise의 Chaining
위 원리는 Promise의 Chaining 기능으로 인해 발생한다.
이해를 쉽게 하기 위해 다음과 같이 Promise를 생성하는 코드가 있다고 가정하자:
function someAsyncJob (willSuccess) {
return new Promise((success, fail) => {
if (willSuccess) {
return success()
}
return fail()
})
}
자바스크립트의 Promise는 비동기 작업이 성공할 경우 then의 첫번째 인자 함수를 실행한다.
const returns = await someAsyncJob(true)
.then(() => console.log('Success!')) // Prints: Success!
console.log(returns) // Prints: undefined
만약 then이 무언가를 return한다면 그 값은 다른 Promise 컨테이너에 담겨 보여지게 된다.
const returns = await someAsyncJob(true)
.then(() => 'Success!')
console.log(returns) // Prints: Success!
그래서 다음과 같은 작업도 가능하다.
const returns = await someAsyncJob(true)
.then(() => 'Success!')
.then((prev) => prev + ' yay!')
console.log(returns) // Prints: Success! yay!
catch도 마찬가지다.
const returns = await someAsyncJob(false)
.then(() => 'Success!')
.then((prev) => prev + ' yay!')
.catch(() => 'Failed...')
console.log(returns) // Prints: Failed...
즉, 오류가 발생했을때는 'Failed'
로 대체하도록 할 수 있다는 것이다.
then의 두번째 인자도 비슷하게 작동한다.
참고로 then의 두번째 인자는 바로 앞 Promise가 Fail 했을때만 작동하고 catch는 then의 두번째 인자로 처리하지 못한 Fail들을 모두 잡는다.
const returns = await someAsyncJob(true)
.then(() => someAsyncJob(false), () => 'Failed First job...')
.then(() => ' Success All!', () => 'Failed Second job...')
.catch(() => 'Failed Something...')
console.log(returns) // Prints: Failed Second job...
마치며...
평상시 async/await
의 습관으로 놓치기 쉬운 Promise의 숨은 기능들을 알아보았다. 쓸일이 있을까 생각할 수 있지만 적절한 부분에서 사용한다면 고급 JavaScript 문법의 편리함을 얻을 수 있다.
만약 틀린 내용이 있거나 Promise에 숨은 다른 사실이 있다면 댓글로 알려주세요!