비동기 처리의 어려움

천영석·2021년 9월 8일
0

어제 잠들기 전에 문득 보또보의 비동기 처리에 대한 코드가 생각났다. async await을 통해 Promise나 콜백 함수보다 한층 더 깔끔해진 비동기 처리를 할 수 있게 되었지만 에러가 발생했을 때의 코드가 섞여있다는 것이 별로라는 생각이 들었다.

아무래도 비동기 처리를 하는 곳마다 try...catch를 사용해야 해서 코드도 길어지고, 성공과 실패에 대한 로직이 섞여 있어서 가독성도 좋지 않은 것 같다. 비동기 코드가 많지 않았을 땐 그렇게 문제라고 생각하지 않았는데 엄청나게 많아지니까 점점 코드 읽기가 힘들어진다.

그렇다고 아무것도 하지 않은 것은 아니다. errorHandler라는 커스텀 훅을 만들어서 catch에서 항상 errorHandler로 에러를 던지고 있다. errorHandler에서는 에러에서 받은 커스텀 코드를 가지고 에러 메시지를 탐색해 스낵바로 에러를 전달해준다. 하지만 이것도 부족하다. 내 목표는try...catch를 없애는 것이다.

try...catch를 없애기 위해 에러 바운더리를 찾아봤다. 예전에 recoil의 selector를 사용하면서 에러 바운더리를 적극적으로 활용해보려고 했지만 이전 컴포넌트가 아닌 에러 컴포넌트 자체를 렌더링시키는 것이기 때문에 스낵바만 띄우고 싶다는 니즈를 충족시키지 못해 포기했었다. 하지만 에러 바운더리를 잘게 쪼개서 가져간다면? 에러가 발생했을 때 에러가 발생한 컴포넌트 자체를 fallback UI로 띄워주고, 그 뒤에 스낵바를 띄워주면 될 것 같았다. 에러가 발생했으면 데이터가 없을 것이기 때문에 기존 화면을 유지하기만 하면 된다. 그런 희망을 가지고 도전해보기로 했다.

하지만 시작부터 막혔다. 아무리 생각해봐도 비동기 요청을 한 뒤, 비동기 요청이 넘어가고 비동기 요청이 완료되었을 땐 콜스택이 비어있을 것이다. 그렇다면 해당 비동기 함수가 어디에서부터 시작된 함수인지 알기 못하기 때문에 비동기 함수 안이나 비동기 함수를 호출하는 함수 안에 try...catch가 없다면 전역으로 에러가 넘어갈 것 같았다. 실제로 이것이 맞는 것 같다. 에러 바운더리에서는 아래의 에러를 catch하지 못한다고 한다.

  • Event handlers (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)

참고 - 리액트 공식문서

즉, 비동기 함수를 catch하지 못한다. 그렇기 때문에 다른 방법을 사용해야 한다. 바로 상태에 에러를 set하는 것이다.(setState를 호출하면서 throw를 할당!) 그렇게 되면 에러가 발생했을 때, 랜더링을 발생시킬 것이고 랜더링이 발생하면 함수가 호출되면서 콜스택이 차곡차곡 쌓일 것이다. 이제, 에러가 전파될 수 있는 환경이 만들어졌다.

const [_, setError] = useState(null);

const asyncFunc = async () => {
  setTimeout(() => {
    setError(() => {
      throw new Error('에러!!')
    })
  }, 3000)
}

이렇게 error를 상태로 set하기만 하면 된다. 하지만!! 여기에서도 내가 원했던 try...catch를 없앨 수는 없었다. 결국은 catch에서 에러를 상태로 set하는 로직이 필요하기 때문이다. 그리고 에러가 발생했을 때, 실행되면 안되는 로직들도 있기 때문에 이것저것 문제가 된다고 생각했다.

에러 바운더리를 사용하기 보다는 한번 더 추상화를 해서 try...catch만을 처리하는 훅을 만들어서 사용해보려고 한다. 어떻게든 성공하는 로직과 실패하는 로직을 분리해보고 싶다.

성공하게 되면 다시 글을 작성하려고 한다.

참고한 자료

profile
느려도 꾸준히 발전하려고 노력하는 사람입니다.

0개의 댓글