| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Tomcat
- shorturl
- IntelliJ
- urlshortner
- mysql
- SQL Server
- svn
- STS
- Eclipse
- mybatis
- Java
- 정보처리기사
- spring
- jquery
- SQL
- javascript
- devlog
- my sql
- dbeaver
- js
- programmers
- html
- maria db
- Mac
- reCAPTCHA
- Oracle
- TIP
- windows
- node.js
- Linux
- Today
- Total
고양의 성장일기
[Javascript] 자바스크립트 비동기 3부작 <async / await> 본문
자바스크립트 비동기 3부작 <async / await>
지난 글에서는 비동기를 처리하는 여러 가지 방법 중 Promise에 대해 알아보았습니다.
이번 글에서는 제가 조금 더 선호하는 비동기 처리 방식인 async / awiat 방식을 탐구해 보겠습니다.
async / await가 뭐죵
async / await는 Promise 기반 비동기 코드를 동기 코드처럼 작성할 수 있도록 해주는 문법적 추상화입니다.
then()이나 catch() 메서드가 아닌, try()-catch() 패턴을 이용해서 성공과 실패를 핸들링하므로
Promise의 메서드 체이닝 방식보다 구조적으로 한층 정돈된 모습을 가지게 됩니다.
즉, 내부적으로는 Promise를 사용하되,
작성 방식은 일반적인 동기코드와 매우 유사하여 가독성과 유지보수성이 뛰어난 문법입니다.
async function fetchData() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error(error);
}
}
이 코드는 일견 동기코드처럼 보이지만 실제로는 비동기 Promise의 흐름 위에서 동작합니다.
어떻게 이런 깔끔한 코드에서 그런 흐름이 가능한지 살펴보겠습니다.
async / await의 역할
async 함수의 특징
async 키워드가 붙은 함수는 반드시 Promise를 반환합니다.
즉, 아래와 같은 메서드 체이닝 흐름이 가능하죠.
async function foo() {
return 10;
}
foo().then(value => {
console.log(value); // 10
});
async 함수 내부의 return은 resolve로, throw는 reject로 자동 변환됩니다.
await의 의미
await는 Promise가 처리될 때까지 함수 실행을 일시적으로 중단하고 결과를 반환받는 키워드입니다.
const result = await promise;
단, await는 async함수 내부에서만 사용 가능합니다.
사용 예제
Promise 방식과 비교해서 어떻게 다른지 예제를 통해 확인해 보겠습니다.
Promise 방식
fetch('/api/data')
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
async / await 방식
async function fetchData() {
try {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
확실히 Promise 방식에 비해 위에서 아래로 뚜렷하게 읽히는 흐름이 되고 중첩구조가 정리된 듯한 느낌입니다.
async / await 좀 더 깊게 알아보기
여러 가지 문법을 알아도 애매하게 알고 사용하면 자칫 이것도 애매하고 저것도 애매한 상황이 발생할 수도 있습니다.

1. await의 병렬 처리
여러 개의 Promise 결과를 await를 이용해 기다릴 때는 어떻게 해야 할까요?
아래처럼 하면 됩니다.
const a = await fetchA();
const b = await fetchB();
사실 그다지 권장되는 방법은 아닙니다.
아래처럼 써보면 어떨까요?
const [a, b] = await Promise.all([
fetchA(),
fetchB()
]);
fetchA가 끝나고 나서야 fetchB를 기다리는 것이 아닌, 하나의 await 안에서 Promise 생성을 동시에 하는 패턴입니다.
2. await의 순차 처리
의도적으로 순차 실행을 보장해야 할 필요가 있을 때는 아래와 같이 사용할 수도 있습니다.
for (const item of items) {
await process(item);
}
조금 더 작업을 명확히 나누어 보자면 아래와 같이 쓸 수 있겠군요.
for (const order of orders) {
await validate(order);
await pay(order);
await saveHistory(order);
}
그런데 왜 굳이 향상된 for문일까요?
forEach문은 await가 의도대로 동작하지 않기 때문입니다.
items.forEach(async item => {
await process(item);
});
forEach는 async를 기다리지 않고 상위 로직이 먼저 종료되기 때문에 에러핸들링이 매우 어렵게 됩니다.
그 때문에 await를 인식하고 흐름을 제어할 수 있는 반복문인 향상된 for문을 활용합니다.
3. for문 안에서 await를 쓰지 않는 편이 좋은 경우
3-1. 성능이 중요한 경우
아래와 같은 경우는 여러개의 호출을 굳이 기다릴 필요가 없습니다.
for (const id of ids) {
await fetchData(id);
}
이런 경우 await와 Promise를 함께 활용해 병렬처리를 해주면 더 쾌적합니다.
await Promise.all(ids.map(fetchData));
3-2. 서로 독립적인 작업
- 단순 데이터 조회
- UI 렌더링용 데이터 수집
- 이미지 프리로딩
이 경우 순차 실행은 불필요한 성능 낭비입니다.
async / await 심화 활용 패턴
실무에서 종종 쓰이는 패턴으로서 알아두면 좋을 것 같습니다.
1. 제한된 병렬처리 (Concurrency Control)
완전 병렬은 좀 위험하고, 완전 순차는 많이 느릴 것 같을 때
asycn function processWithLimit(items, limit) {
const result = [];
const executing = [];
for (const item of items) {
const p = process(item).then(res => {
executing.splice(executing.indexof(p), 1);
return res;
})
results.push(p);
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
사용 사례
- 대량 API 호출
- 외부 서비스 rate limit 대응
2. 단계별 파이프라인 처리
한 단계씩 안전하게 처리하고 싶을 때
async function pipeline(data) {
const step1 = await validate(data);
const step2 = await transform(step1);
const step3 = await save(step2);
return step3;
}
특징
- 가독성 극대화
- 트랜잭션 흐름 표현에 적합
반복 + 부분 병렬 처리 혼합
그룹 단위 순차 - 그룹 내부 병렬
for (const group of groups) {
await Promise.all(group.map(process));
}
사용 사례
- 배치 처리
- 페이지 단위 데이터 처리
async 재귀 패턴
트리구조나 파일시스템, 댓글 대댓글 구조 등을 처리할 때
async function traverse(node) {
await process(node);
for (const child of node.children) {
await traverse(child);
}
}
재시도(Retry) 로직
제한된 횟수 내에서 스스로를 재시도할 때
async function retry(fn, retries = 3) {
try {
return await fn();
} catch (e) {
if (retries === 0) throw e;
return retry(fn, retries - 1);
}
}
- 네트워크 불안정 대응
- 외부 API 안정성 확보
타임아웃과 await 조합
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
await Promise.race([
fetchData(),
timeout(3000)
]);
마치며
async / await는 비동기 처리를 위한 완전한 별개의 문법이 아닌, Promise를 더 읽기 쉽고 안전하게 사용하는 방법입니다
중요한 것은 단순히 기다리는 문법이 아닌 “흐름을 설계하는 도구”로서 이해해야 한다는 점입니다.
- async는 Promise를 반환하고
- await는 Promise를 기다리며
- 에러는 try/catch로 관리하고
- 병렬 처리는 명시적으로 설계
저에게 하나의 소망이 있다면, 더 이상 데이터를 가져와서 화면에 렌더링 하는 등의 연쇄적인 로직을 콜백으로 처리하지 않고
Promise든 async / await든 비동기로 논리적 블록을 구분하여 작성하는 스킬을 얻으셨기를 하는 바람입니다.
다음 글로는 비동기 3부작의 마지막인 callback 방식으로 찾아뵙겠습니다.
감사합니다!
'🖥️ Front-End > Javascript' 카테고리의 다른 글
| [Javascript] 싱글 스레드인 자바스크립트가 비동기 작업을 수행하는 방법 (0) | 2025.12.17 |
|---|---|
| [Javascript] 자바스크립트 비동기 3부작 <Callback> (0) | 2025.12.17 |
| [Javascript] 자바스크립트 비동기 3부작 <약속의 Promise> (0) | 2025.12.15 |
| [Javascript] Fisher-Yates 알고리즘으로 랜덤 값 가져오기 (3) | 2025.12.15 |
| [Javascript] "==" 그리고 "===" (0) | 2025.10.10 |
