Javascript 비동기 처리

김강민·2025년 8월 6일

개발

목록 보기
21/32
post-thumbnail

async/await vs Promise.all vs Promise.allSettled

JavaScript에서 비동기(Asynchronous) 처리는 필수적이다. 콜백 지옥을 해결하기 위해 Promise가 등장했고, 그 Promise를 더 읽기 쉽게 만들기 위해 async/await 문법이 도입되었다. 하지만 async/await만으로는 모든 비동기 시나리오를 효율적으로 처리할 수 없다. 특히 여러 개의 비동기 작업을 동시에 처리해야 할 때, 우리는 Promise.allPromise.allSettled라는 강력한 도구를 사용해야 한다.

그럼 이 세 가지 개념이 서로 어떤 특징을 가지고, 어떤 차이점이 있는지, 언제 사용하는지에 대해 알아보자.

1. async/await

비동기 코드를 동기적으로

async/awaitPromise를 기반으로 동작하는 문법적 설탕(Syntactic Sugar)이다. 이 문법의 핵심적인 존재 이유는 단 하나, 비동기 코드를 동기 코드처럼 읽고 쓸 수 있게 만들어 가독성을 극대화하는 것이다.

장점: 압도적인 가독성과 에러 핸들링

.then() 체이닝 방식에 비해, async/awaittry...catch 구문을 사용하여 코드의 흐름이 위에서 아래로 자연스럽게 이어지므로 로직을 파악하기가 훨씬 쉽다.

// Promise (.then)
function fetchDataWithPromise() {
  fetch('api/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));
}

// async/await
async function fetchDataWithAsync() {
  try {
    const response = await fetch('api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

단점: 순차적 실행으로 인한 비효율

async/await의 가장 큰 함정은, 각 await 키워드가 해당 Promise가 완료될 때까지 코드 실행을 '일시 정지'시킨다는 점이다. 만약 서로 의존성이 없는 여러 비동기 작업을 await로 순차적으로 처리하면, 심각한 성능 저하를 유발할 수 있다.

async function fetchMultipleData() {
  // fetchUser(1)이 끝나기를 기다렸다가 (1초 소요)
  const user = await fetchUser(1);
  // 그 다음에 fetchPosts()를 실행하고 기다린다 (1초 소요)
  const posts = await fetchPosts();

  // 총 소요 시간: 2초
  return { user, posts };
}

2. Promise.all

병렬 처리로 성능 극대화

Promise.all은 위와 같은 비효율을 해결하기 위해 존재한다. 서로 의존성이 없는 여러 개의 Promise를 동시에(in parallel) 실행하고, 모든 Promise가 완료될 때까지 한 번만 기다린다.

장점: 성능 최적화

Promise.all은 여러 비동기 작업을 병렬로 처리하여, 전체 실행 시간을 가장 오래 걸리는 작업 하나에 맞춰준다.

async function fetchMultipleDataParallel() {
  // 두 요청을 동시에 시작한다.
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts()
  ]);

  // 총 소요 시간: 1초
  return { user, posts };
}

단점: All or Nothing (하나라도 실패하면 전부 실패)

Promise.all의 가장 큰 특징이자 단점은, 전달된 Promise단 하나라도 실패(reject)하면, 즉시 전체가 실패하고 다른 Promise의 성공 결과는 모두 무시된다는 점이다.

try {
  const results = await Promise.all([
    Promise.resolve('성공1'),
    Promise.reject('실패!'), // 이 Promise가 실패하는 순간
    Promise.resolve('성공2')  // 이 Promise의 결과는 받지 못한다.
  ]);
} catch (error) {
  console.error(error); // "실패!"
}

"모든 작업이 반드시 성공해야만 다음 단계로 넘어갈 수 있는" 시나리오에 적합하다.

3. Promise.allSettled

실패에 관대한 병렬 처리

Promise.allSettledPromise.all의 'All or Nothing' 문제를 해결하기 위해 등장했다. 전달된 모든 Promise가 성공하든 실패하든 상관없이, 모든 작업이 끝날 때까지 기다렸다가Promise의 결과를 모아서 반환한다.

장점: 높은 안정성과 유연성

Promise.allSettled는 절대 실패(reject)하지 않는다. 대신, 각 결과에 대해 { status: 'fulfilled', value: ... } 또는 { status: 'rejected', reason: ... } 형태의 객체 배열을 반환한다. 이를 통해 개발자는 각 작업의 성공/실패 여부를 직접 확인하고 개별적으로 처리할 수 있다.

const results = await Promise.allSettled([
  Promise.resolve('성공1'),
  Promise.reject('실패!'),
  Promise.resolve('성공2')
]);

/*
results 결과:
[
  { status: 'fulfilled', value: '성공1' },
  { status: 'rejected',  reason: '실패!' },
  { status: 'fulfilled', value: '성공2' }
]
*/

const successfulResults = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value); // ['성공1', '성공2']

"여러 작업 중 일부가 실패하더라도, 성공한 작업의 결과는 반드시 필요한" 시나리오에 매우 유용하다.

🤔 그렇다면 언제 무엇을 써야 할까?

구분async/await (순차적)Promise.allPromise.allSettled
실행 방식순차적 (Sequential)병렬적 (Parallel)병렬적 (Parallel)
에러 처리하나가 실패하면 즉시 catch 블록으로 이동하나라도 실패하면 전체가 즉시 실패모든 작업이 끝난 후, 각 결과의 status로 성공/실패 판단
주요 사용처비동기 작업 간에 순서(의존성)가 있을 때모든 작업이 반드시 성공해야 할 때일부 작업이 실패해도 나머지 결과가 필요할 때

async/await는 비동기 코드의 가독성을 위한 기본 문법이며, Promise.allPromise.allSettled서로 의존성이 없는 여러 비동기 작업을 동시에 처리하여 성능을 최적화하기 위한 강력한 도구이다. 이들은 서로 경쟁하는 관계가 아니라, 함께 사용될 때 비로소 비동기 코드의 가독성과 성능을 모두 잡을 수 있는 최고의 파트너가 된다.

profile
인생은 프레임워크처럼, 공부는 라이브러리처럼

0개의 댓글