Promise.all과 구조 분해 할당을 활용한 데이터 처리 패턴
가독성과 퍼포먼스를 모두 잡는 모던 자바스크립트 비동기 패턴
자바스크립트에서 복수의 비동기 데이터(API, 파일 시스템 등)를 처리할 때 단순히 await를 순차적으로 나열하는 방식은 동기적 정체를 유발한다.
모던 자바스크립트(ES6+)는 Promise.all과 구조 분해 할당(Destructuring Assignment)을 결합하여, 여러 비동기 요청을 병렬로 처리함과 동시에 결과 배열의 깊은 오브젝트 내부 속성까지 한 줄로 추출하는 강력한 표현력을 제공한다.
1. 순차적 await와 병렬 Promise.all의 실행 흐름 차이
독립적인 비동기 연산 여러 개를 연이어 수행할 때, 직관적으로 작성된 코드는 대개 앞선 연산이 끝나기를 기다리는 병목을 발생시킨다.
// ❌ 순차적 실행: 총 2초 이상 소요되는 비효율적인 패턴
const postsResult = await useAsyncData('posts', ...) // 1초 대기
const navResult = await useAsyncData('nav', ...) // 앞선 연산 종료 후 1초 대기
반면 Promise.all은 인자로 전달된 Promise 배열을 동시에 실행하여 병렬 처리한다.
모든 Promise가 Fulfilled 상태가 되는 시점에 결과값들을 원본 요청 순서에 맞는 배열 형태로 반환한다.
// ✨ 병렬 실행: 동시에 요청하여 총 1초 소요
const results = await Promise.all([
useAsyncData('posts', ...),
useAsyncData('nav', ...)
])
// results => [posts의 결과 오브젝트, nav의 결과 오브젝트]
2. 구조 분해 할당(Destructuring)의 핵심 메커니즘
구조 분해 할당은 배열 리터럴([ ]) 또는 객체 리터럴({ }) 형태를 좌항에 선언하여 우항의 데이터 구조에서 필요한 값만 추출하는 문법이다.
단순히 results[0]이나 results.data 같은 접근 코드를 줄이는 것이 아니라, 데이터 구조 자체를 코드에 표현하는 선언적인 방식이다.
📦 2-1) 배열 구조 분해 할당의 순서 매핑
Promise.all이 반환하는 결과는 순서가 보장되는 배열이다.
좌항에 배열 형태를 작성하면 우항 배열의 인덱스 순서에 따라 값이 자동으로 매핑된다.
// 배열의 0번째와 1번째 결과를 각각 변수에 할당
const [postsResult, navResult] = await Promise.all([
useAsyncData('posts', ...),
useAsyncData('nav', ...)
])
동작 구조:
Promise.all 결과
[
posts 결과,
nav 결과
]
↓
const [
postsResult,
navResult
]
↓
각 변수에 순서대로 할당
🗂️ 2-2) 객체 구조 분해 할당과 키(Key) 매핑
객체 구조 분해 할당은 배열과 다르게 순서가 아닌 프로퍼티 Key를 기준으로 값을 찾는다.
const {
data,
pending,
error
} = result
객체 내부 구조:
{
data: [...],
pending: false,
error: null
}
↓
{
data,
pending,
error
}
↓
동일한 Key 기준으로 값 추출
객체의 순서가 변경되어도 Key 이름만 동일하면 정상적으로 값을 가져온다.
3. 중첩된 객체 구조 분해와 변수명 재정의(Aliasing)
비동기 데이터 처리 라이브러리는 단순한 데이터만 반환하지 않는다.
일반적으로 다음과 같은 Wrapper Object 형태를 반환한다.
{
"data": [],
"pending": false,
"error": null
}
이 중 필요한 값만 추출하고 새로운 변수명을 지정할 수 있다.
3-1) 🏷️ 변수명 재정의 (Aliasing)
객체 구조 분해에서는 다음과 같은 문법을 사용할 수 있다.
// data 속성을 posts라는 이름으로 변경
const { data: posts } = await useAsyncData('posts', ...)
동작:
{
data: [...]
}
↓
data 추출
↓
posts 변수에 할당
이를 통해 변수 이름 충돌을 방지하고 현재 Context에 맞는 이름을 사용할 수 있다.
3-2) 🛡️ 기본값(Default Value) 지정
구조 분해 할당에서는 값이 존재하지 않을 경우를 대비해 기본값을 지정할 수 있다.
// data가 undefined인 경우 빈 배열 사용
const { data: posts = [] } = await useAsyncData('posts', ...)
동작:
data 존재
↓
data 값 사용
data 없음
↓
[] 기본값 사용
비동기 요청 실패나 초기 렌더링 과정에서 안전하게 데이터를 처리할 수 있다.
4. 최종 형태: 복합 중첩 구조 분해
배열 구조 분해 내부에 객체 구조 분해, Alias, 기본값 설정을 함께 적용하면 다음과 같은 형태가 된다.
const [
{ data: posts = [] },
{ data: navData = [] }
] = await Promise.all([
useAsyncData('main-posts-feed', ...),
useAsyncData('main-posts-nav', ...)
])
위 코드는 다음 과정을 한 번에 수행한다.
Promise.all 실행
↓
[
posts Response Object,
nav Response Object
]
↓
첫 번째 객체에서 data 추출
↓
posts 변수 저장
두 번째 객체에서 data 추출
↓
navData 변수 저장
🔍 동작 프로세스 분석
1. 병렬 요청
Promise.all 내부의 비동기 함수들이 동시에 실행된다.
Request A 시작
+
Request B 시작
순차 실행이 아니기 때문에 전체 실행 시간은 가장 오래 걸리는 요청 기준으로 결정된다.
2. 결과 배열 생성
모든 Promise가 완료되면 Promise.all은 결과 배열을 반환한다.
[
Response Object A,
Response Object B
]
3. 배열 분해
좌항의 배열 구조 분해가 실행되어 각각의 결과를 분리한다.
[
Object A,
Object B
]
↓
[
first,
second
]
4. 객체 내부 값 추출
각 Object 내부의 data 속성을 찾아 변수에 저장한다.
Object A.data
↓
posts
Object B.data
↓
navData
📌 정리
복합 구조 분해 할당은 여러 단계에 걸쳐 작성해야 했던 데이터 접근 코드를 하나의 선언으로 표현할 수 있게 해준다.
Promise.all과 함께 사용하면 다음과 같은 장점을 얻을 수 있다.
Promise.all
↓
비동기 요청 병렬 처리
+
Destructuring
↓
필요한 데이터만 추출
+
Aliasing
↓
명확한 변수명 사용
+
Default Value
↓
안정적인 예외 처리
결과적으로 모던 자바스크립트의 비동기 패턴은 단순히 실행 속도를 개선하는 것을 넘어, 복잡한 데이터 흐름을 더 읽기 쉽고 유지보수 가능한 코드로 표현할 수 있도록 발전했다.
특히 Nuxt와 같은 데이터 페칭 기반 프레임워크에서는 여러 API 요청을 동시에 처리하고 필요한 상태만 추출하는 패턴이 자주 사용되므로, Promise.all과 구조 분해 할당 조합은 중요한 개발 패턴으로 활용된다.
