일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- react
- HTML
- 클라우드
- kubernetes
- 백엔드
- 이더리움
- 자바스크립트
- TypeScript
- 블록체인
- 파이썬
- 알고리즘
- k8s
- 컴퓨터공학
- 리액트
- 가상화
- 솔리디티
- CSS
- JavaScript
- 프론트엔드
- docker
- 백준
- 이슈
- 타입스크립트
- BFS
- es6
- AWS
- next.js
- VUE
- 쿠버네티스
- 웹
- Today
- Total
즐겁게, 코드
Promise.all 로 비동기 로직 개선하기 본문
아주 오랜 옛날부터, 전설로 내려오는 코드가 있습니다.
(3학년 1학기에 수행했던 산학프로젝트 코드입니다.)
이 코드는 간단한 그룹 매칭 서비스에서 그룹을 생성하는 역할인데요, 여러 비동기 로직이 async-await
을 통해 순차적으로 실행되는 무시무시한 모습입니다.
얼핏 보면 비동기 로직 제어에 성공한 것처럼 보이지만 순차적으로 실행되는 약 7회의 POST 요청으로 인해 당시 그룹을 생성하는 데에만 약 20~30초 가량의 시간이 소요되었고, UX 개선을 위해 로딩 애니메이션을 별도로 제작하는 등의 수고를 더해야만 했습니다.
콜백, 프라미스, async-await
문법을 거쳐오며 비동기 로직 제어는 점점 쉬워졌지만 진화의 최종형인 async-await
을 사용하는 것만이 언제나 답이 되지는 않는데요, 오늘은 위 문제를 해결할 수 있는 Promise.all
문법에 대해 소개해보도록 하겠습니다.
async-await, 과연 무적인가?
먼저 기존의 async-await
은 그 이름이 시사하듯 비동기 동작(프라미스)의 상태가 완료될 때까지 기다린 후, 다음 코드를 순차적으로 읽어 나가며 실행합니다.
한번 실행하는데 각각 1초 / 2초가 걸리는 쿼리를 수행하는 예제를 생성해보도록 하겠습니다.
const userList = [
{ name: 'ethan', id: 1 },
{ name: "david", id: 2 },
{ name: 'john', id: 3 }
];
// 1초가 걸리는 쿼리
const getUserById = async (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const [user] = userList.filter(user => user.id === id)
resolve(user)
}, 1000)
})
}
// 2초가 걸리는 쿼리
const getAllUsers = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(userList)
}, 2000)
})
}
const fetchData = async () => {
console.time('소요시간 : ')
const user = await getUserById(2)
const userList = await getAllUsers()
// 불러온 데이터를 출력하기까지 최소 3초가 소요된다.
console.log(user)
console.log(userList)
console.timeEnd('소요시간 : ')
}
fetchData()
async-await
은 태스크의 순서가 보장되어야 할 때는 분명 강력하지만, 각각의 태스크가 서로 연관성이 없는 작업일 때는 앞의 작업이 끝날 때까지 기다릴 필요가 없습니다.
자, 뭔가 다른 방법은 없을까요?
Promise.all
바로 이런 상황에서 Promise.all
이 빛을 발할 수 있습니다.
Promise.all
은 여러 비동기 동작(프라미스)을 하나로 묶어 하나의 프라미스처럼 관리할 수 있게 해주는데요, 위의 코드를 Promise.all
을 사용해 개선해 보겠습니다.
const userList = [
{ name: 'ethan', id: 1 },
{ name: "david", id: 2 },
{ name: 'john', id: 3 }
];
const getUserById = async (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const [user] = userList.filter(user => user.id === id)
resolve(user)
}, 1000)
})
}
const getAllUsers = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(userList)
}, 2000)
})
}
const fetchData = async () => {
console.time('소요시간 :')
// Promise.all() 은 실행할 비동기 태스크들이 담긴 배열을 인자로 받습니다.
const [user, userList] = await Promise.all([getUserById(2), getAllUsers()])
console.log(user)
console.log(userList)
console.timeEnd('소요시간 :')
}
fetchData()
Promise.all
은 여러 비동기 태스크를 동시에(병렬적으로) 실행하고, 가장 마지막 태스크가 완료될 때 완료 상태의 프라미스를 반환합니다.
따라서 위의 예제의 실행시간은 모든 태스크를 더한 1초 + 2초가 아닌, 가장 오래 걸린 태스크의 실행시간인 2초 남짓한 시간만이 소요된 모습입니다.
주의사항
Promise.all
은 잘 사용하면 비동기 로직의 완료시간을 크게 줄여줄 수 있는 문법이지만, 반드시 짚고 넘어가야 할 점이 하나 있습니다.
바로 Promise.all
내부에서는 태스크들의 순서가 제어되지 않기 때문에, 태스크의 순서가 중요한 경우라면 절대로 Promise.all
을 통해 관리해서는 안된다는 점입니다.
TL;DR;
- 실행할 비동기 태스크들의 순서가 중요하지 않다면,
Promise.all
로 완료시간을 크게 앞당길 수 있다.
(주로 여러 데이터를 페칭하는 경우)
const fetchInitialData = async () => {
// 여러 데이터를 페칭할 경우, 병렬적으로 비동기 로직을 수행할 수 있다!
const [userData, companyData, scoreData] = await Promise.all([
getUserList(),
getCompanyList(),
getScoreList()
])
console.log(userData, companyData, scoreData);
}
- 단, 각 태스크의 실행 순서가 중요한 경우라면 기존의
async-await
을 사용하자!
(주로 여러 데이터의 페칭과 뮤테이션을 함께 수행하는 경우)
const setInitialData = async () => {
// update, get의 순서가 보장되어야 하는 경우
const userData = await getUserList();
await updateUserList({name: "chanmin", age: 25});
}
'💬 언어 > Javascript' 카테고리의 다른 글
인터섹션 옵저버로 인터섹션 여부 감지하기 (1) | 2022.01.09 |
---|---|
&& 연산자에 대한 간단한 고찰 (1) | 2021.12.10 |
HTMLCollection과 NodeList, 너흰 누구니? (1) | 2021.10.24 |
자바스크립트 메모리 누수 방지 기법 (0) | 2021.09.12 |
얼어붙어라! - 객체 불변화하기 (0) | 2021.08.26 |