Javascript

try-catch와 .catch

개요 회사 코드를 살펴보던 중 Redux 부분에서 saga를 통해 API를 호출하고 데이터를 받아오는 과정에서 API 요청은 .then.catch를 사용해서 받아오고, API를 랩핑하는 함수는 try-catch를 사용하고 있었습니다. 그러다 이 둘로 구분한 이유와 try-catch문의…

2021. 08. 15읽기 3분1789자

개요

회사 코드를 살펴보던 중 Redux 부분에서 saga를 통해 API를 호출하고 데이터를 받아오는 과정에서 API 요청은 .then.catch를 사용해서 받아오고, API를 랩핑하는 함수는 try-catch를 사용하고 있었습니다. 그러다 이 둘로 구분한 이유와 try-catch문의 중첩에 관해서 궁금증이 생겼습니다.

먼저 다음 코드는 redux-saga에서 사용된 .then.catch와 try-catch의 혼합 형태입니다.

const funcAPI = (payload) => {
	return API.defaultAPI
    .get("/...", {
      params: {
        ...
      },
    })
    .then((res) => res.data.data)
    .catch((err) => {
      if (err.response.status !== 200) {
        return {
          error: {
            code: err.response.status,
            description: err.response.statusText,
          },
        };
      }
      throw err;
    });
 
function* funcProcess(action) {
  try {
		const result = yield call(funcAPI, actions.payload);
    if (result.error) {
      if (action.payload.onError) {
        yield call(
          action.payload.onError,
          result.error.status.code
        );
      }
    } else {
      if (action.payload.onSuccess) {
        yield call(action.payload.onSuccess, result);
      }
    }
  } catch (err) {
    console.log("from saga", err);
  }
}

여기서 생기는 궁금증은 funcProcess에서 result에 error를 받았는데 왜 catch로 넘어가지 않는지, try문 안에 따로 result.error를 만든 이유가 무엇인지, 어떻게 동작하는지에 대한 의문점이 들었고 이를 파악해 보기로 했습니다.

동작원리

먼저 동작 원리를 파악하기 위해 다음과 같은 심플한 코드를 작성해보았습니다.

데이터를 성공적으로 받아오는 경우

const promiseA = () =>
  new Promise((resolve, reject) => {
    resolve('Success')
    /* reject(new Error("Error1")) */
  })
 
const funcA = () =>
  promiseA()
    .then((data) => data)
    .catch((err) => {
      throw err
    })
 
const resultA = async () => {
  try {
    const data = await funcA()
    console.log(`success: ${data}`)
  } catch (err) {
    console.log(`fail: ${err}`)
  }
}
 
resultA()
  1. resultA 함수를 호출하면 funcA 함수를 호출하게 되고, funcA 함수는 promiseA를 호출하게 됩니다.
  2. promiseA에서 성공 값을 넘겨주는 resolve를 실행시키면 resolve 안의 "Success"라는 데이터를 넘겨줍니다.
  3. 따라서 funcA에서는 promiseA에서 에러 없이 값을 전달받아 then 구문이 실행되고, funcA는 "Success"라는 데이터를 리턴해줍니다.
  4. 최종적으로 resultA는 "Success" 메시지를 받아 "success: Success"라는 메시지가 콘솔에 찍히게 됩니다.

데이터를 성공적으로 받아오지 못한 경우

const promiseA = () =>
  new Promise((resolve, reject) => {
    reject(new Error('Error1'))
  })
 
const funcA = () =>
  promiseA()
    .then((data) => data)
    .catch((err) => {
      throw err
    })
 
const resultA = async () => {
  try {
    const data = await funcA()
    console.log(`success: ${data}`)
  } catch (err) {
    console.log(`fail: ${err}`)
  }
}
 
resultA()
  1. resultA 함수를 호출하면 funcA 함수를 호출하게 되고, funcA 함수는 promiseA를 호출하게 됩니다.
  2. promiseA에서 실패하여 reject를 실행시키면 reject 안의 "Error1"이라는 데이터를 넘겨줍니다.
  3. 따라서 funcA에서는 promiseA에서 에러 값("Error1")을 전달받아 catch 구문이 실행되고, funcA는 "Error1" 에러를 던집니다.
  4. 최종적으로 resultA는 "Error1" 에러를 받아 catch 문을 타고 "fail: Error: Error1" 메시지가 콘솔에 찍히게 됩니다.

궁금점1

💡 데이터를 성공적으로 받아오지 못한 경우, 중간 catch문에서 리턴문 실행을 실행하면 어떻게 될까?

const promiseA = () =>
  new Promise((resolve, reject) => {
    reject(new Error('Error1'))
  })
 
const funcA = () =>
  promiseA()
    .then((data) => data)
    .catch((err) => {
      if (true) {
        return `Error2`
      }
      throw err
    })
 
const resultA = async () => {
  try {
    const data = await funcA()
    console.log(`success: ${data}`)
  } catch (err) {
    console.log(`fail: ${err}`)
  }
}
 
resultA()
  1. resultA 함수를 호출하면 funcA 함수를 호출하게 되고, funcA 함수는 promiseA를 호출하게 됩니다.
  2. promiseA에서 실패하여 reject를 실행시키면 reject 안의 "Error1"이라는 데이터를 넘겨줍니다.
  3. 따라서 funcA에서는 promiseA에서 에러 값("Error1")을 전달받아 catch 구문이 실행됩니다.
  4. 하지만 funcA의 catch문에서 'Error2' 값을 리턴해줍니다.
  5. 최종적으로 resultA는 에러를 받지 않고 "Error2"라는 메시지 데이터를 받아 catch 문을 타지 않고 "success: Error2" 메시지가 콘솔에 찍히게 됩니다.

👉 결론: resultA에서 try-catch를 하는데 funcA를 분리하여 .then.catch를 사용한 이유는 API 호출 에러 발생 시 중간에 미들웨어처럼 에러에 대한 분기 처리를 하기 위해 사용했던 것으로 추측됩니다.

궁금증2

💡 그렇다면 왜 하나는 try-catch를 쓰고 하나는 .then.catch를 사용했을까요? 하나로 통일하면 안 되는 이유가 있을까요?

const promiseA = () =>
  new Promise((resolve, reject) => {
    reject(new Error('Error1'))
  })
 
const funcA = async () => {
  try {
    const data = await promiseA()
    return data
  } catch (err) {
    if (true) {
      return `Error2`
    }
    throw err
  }
}
 
const resultA = async () => {
  try {
    const data = await funcA()
    console.log(`success: ${data}`)
  } catch (err) {
    console.log(`fail: ${err}`)
  }
}
 
resultA()

위와 같이 funcA를 try-catch문으로 수정했을 때, 자바스크립트에서는 아마 문제가 없을 것입니다. 하지만 타입스크립트의 경우 funcA에서 catch문의 err 타입을 지정해주려고 했을 때,

Catch clause variable type annotation must be 'any' or 'unknown' if specified.

다음과 같은 에러가 발생했습니다.

👉 결론: 아마 전임자는 에러의 타입을 지정해줄 때 .then.catch문의 경우 에러의 타입을 지정해도 에러가 뜨지 않기 때문에 이런 식으로 코드를 작성했던 것으로 추측됩니다. 그렇다면 try-catch문에서 에러의 타입을 지정할 수 없을까라는 생각에 찾아보았고, Type Guards 라는 해결책을 찾았습니다.

추가 궁금증!

💡 async 함수를 사용할 때, try-catch문을 중복해서 사용하는 것에 대한 궁금증도 생겼고, 다음 블로그 포스팅을 통해 해결하였다.

async 함수와 try-catch

도움이 됐다면 공유해 주세요.

같은 카테고리의 글 · Javascript