ES6 Promise 체이닝에서 에러 핸들링 개선하기

문제 상황

회원가입 플로우에서 여러 API를 순차적으로 호출하는 과정에서 에러가 발생했을 때, 어느 단계에서 실패했는지 파악하기 어려웠다.

fetch('/api/user')
  .then(res => res.json())
  .then(user => fetch(`/api/profile/${user.id}`))
  .then(res => res.json())
  .then(profile => updateUI(profile))
  .catch(err => console.error(err));

위 코드는 어느 fetch가 실패했는지, JSON 파싱이 실패했는지 구분이 안 됐다.

해결 방법

각 단계마다 명시적인 에러 정보를 추가하고, 커스텀 에러 객체를 사용했다.

class APIError extends Error {
  constructor(message, stage, response) {
    super(message);
    this.stage = stage;
    this.response = response;
  }
}

fetch('/api/user')
  .then(res => {
    if (!res.ok) throw new APIError('User fetch failed', 'USER_FETCH', res);
    return res.json();
  })
  .then(user => {
    return fetch(`/api/profile/${user.id}`)
      .then(res => {
        if (!res.ok) throw new APIError('Profile fetch failed', 'PROFILE_FETCH', res);
        return res.json();
      });
  })
  .then(profile => updateUI(profile))
  .catch(err => {
    if (err instanceof APIError) {
      console.error(`Failed at ${err.stage}:`, err.message);
      showErrorToUser(err.stage);
    } else {
      console.error('Unexpected error:', err);
    }
  });

결과

에러 로그에서 실패 지점을 즉시 파악할 수 있게 되었고, 사용자에게도 구체적인 에러 메시지를 보여줄 수 있게 되었다. Promise 체이닝이 깊어질수록 이런 패턴의 효과가 컸다.