async/await로 콜백 지옥 탈출하기

문제 상황

사용자 인증 후 프로필 조회, 권한 체크, 로그 기록을 순차적으로 처리하는 API를 작성하던 중 Promise 체이닝이 길어지면서 에러 핸들링이 복잡해졌다.

function getUserData(userId) {
  return authenticate(userId)
    .then(user => checkPermission(user.id))
    .then(permission => {
      if (!permission.allowed) throw new Error('No permission');
      return getProfile(permission.userId);
    })
    .then(profile => logAccess(profile.id).then(() => profile))
    .catch(err => handleError(err));
}

async/await 적용

Node 8부터 정식 지원되는 async/await를 사용해 동기 코드처럼 작성했다.

async function getUserData(userId) {
  try {
    const user = await authenticate(userId);
    const permission = await checkPermission(user.id);
    
    if (!permission.allowed) {
      throw new Error('No permission');
    }
    
    const profile = await getProfile(permission.userId);
    await logAccess(profile.id);
    
    return profile;
  } catch (err) {
    handleError(err);
  }
}

결과

  • 코드 가독성이 크게 개선됨
  • try-catch로 에러 핸들링 일관성 확보
  • 중간 변수 활용이 자유로워져 디버깅 편의성 증가

주의사항

병렬 처리가 필요한 경우 Promise.all을 함께 사용해야 한다. await를 순차적으로 사용하면 불필요하게 대기 시간이 늘어난다.

// 병렬 처리
const [user, settings] = await Promise.all([
  fetchUser(id),
  fetchSettings(id)
]);

ES2017 스펙이지만 Babel 트랜스파일 없이 Node 8에서 바로 사용 가능한 점이 마음에 든다.