| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- CSS
- 테스트자동화
- SpringBoot
- 인텔리제이
- junit5
- math
- HashMap
- Eclipse
- vscode
- javascript
- Visual Studio Code
- 배열
- html
- string
- IntelliJ
- 자바스크립트
- 자바문법
- js
- 자바
- java테스트
- junit
- Array
- Java
- 스프링부트
- 단위테스트
- ArrayList
- 정규식
- list
- input
- 문자열
- Today
- Total
어제 오늘 내일
JavaScript 비동기 처리 완전 정복: async/await 사용법 본문
JavaScript로 개발을 하다 보면 서버에서 데이터를 가져오거나, 파일을 읽거나, 타이머를 다루는 등 시간이 걸리는 작업을 자주 만나게 됩니다. 이런 작업들을 효율적으로 처리하기 위한 핵심 도구가 바로 async/await입니다. 이 글에서는 비동기 처리가 왜 필요한지부터, async/await를 실전에서 어떻게 활용하는지까지 차근차근 살펴보겠습니다.
1. 비동기 처리가 필요한 이유
JavaScript는 기본적으로 싱글 스레드로 동작합니다. 즉, 한 번에 하나의 작업만 처리할 수 있습니다. 그런데 만약 네트워크 요청처럼 오래 걸리는 작업이 끝날 때까지 다음 코드 실행을 멈추고 기다린다면 어떻게 될까요? 화면이 멈추고, 사용자는 아무것도 할 수 없는 답답한 경험을 하게 될 것입니다.
이 문제를 해결하기 위해 JavaScript는 비동기(asynchronous) 방식을 사용합니다. 오래 걸리는 작업은 백그라운드에 맡겨두고, 그동안 다른 코드를 계속 실행하다가 작업이 끝나면 결과를 받아 처리하는 방식입니다.
2. 콜백에서 Promise, 그리고 async/await로
비동기 처리 방식은 시간이 지나면서 계속 발전해왔습니다.
2-1. 콜백 함수 (Callback)
가장 초기 방식은 콜백 함수였습니다.
function getUser(id, callback) {
setTimeout(() => {
callback({ id, name: "홍길동" });
}, 1000);
}
getUser(1, (user) => {
console.log(user);
getUser(2, (user2) => {
console.log(user2);
getUser(3, (user3) => {
console.log(user3);
// 콜백이 계속 중첩되면서 코드가 옆으로 길어짐
});
});
});콜백이 중첩될수록 코드가 오른쪽으로 계속 깊어지는 이른바 "콜백 지옥(Callback Hell)" 현상이 발생해 가독성이 크게 떨어집니다.
2-2. Promise의 등장
이를 개선하기 위해 ES6에서 Promise가 도입되었습니다.
function getUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: "홍길동" });
}, 1000);
});
}
getUser(1)
.then((user) => {
console.log(user);
return getUser(2);
})
.then((user2) => {
console.log(user2);
return getUser(3);
})
.then((user3) => {
console.log(user3);
})
.catch((error) => {
console.error(error);
});.then() 체이닝 덕분에 콜백 지옥은 어느 정도 해결됐지만, 로직이 복잡해지면 여전히 코드가 길어지고 흐름을 한눈에 파악하기 어려운 경우가 많습니다.
2-3. async/await의 등장
ES2017(ES8)부터 도입된 async/await는 Promise를 기반으로 하면서도, 마치 동기 코드를 작성하듯 비동기 코드를 작성할 수 있게 해줍니다.
function getUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: "홍길동" });
}, 1000);
});
}
async function main() {
const user1 = await getUser(1);
console.log(user1);
const user2 = await getUser(2);
console.log(user2);
const user3 = await getUser(3);
console.log(user3);
}
main();훨씬 직관적이고 읽기 쉬운 코드가 되었습니다.
3. async/await 기본 문법
3-1. async 키워드
함수 앞에 async를 붙이면 해당 함수는 항상 Promise를 반환하는 비동기 함수가 됩니다.
async function sayHello() {
return "안녕하세요";
}
sayHello().then((msg) => console.log(msg)); // "안녕하세요"함수 안에서 일반 값을 return해도, 자동으로 그 값을 담은 Promise로 감싸져서 반환됩니다.
3-2. await 키워드
await는 async 함수 내부에서만 사용할 수 있으며, Promise가 처리(resolve 또는 reject)될 때까지 함수 실행을 잠시 멈추고 기다립니다.
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
}await를 만나면 해당 Promise가 완료될 때까지 기다린 후, 그 결과값을 반환합니다. 이때 전체 프로그램이 멈추는 것이 아니라, 해당 async 함수의 실행만 잠시 대기 상태가 되고 다른 코드는 계속 실행될 수 있습니다.
4. 에러 처리: try/catch
Promise에서는 .catch()로 에러를 처리했다면, async/await에서는 익숙한 try/catch 문법을 그대로 사용할 수 있습니다.
async function getUserData(id) {
try {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`요청 실패: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("사용자 정보를 가져오는 중 오류 발생:", error.message);
return null;
} finally {
console.log("요청 처리가 완료되었습니다.");
}
}try 블록 안에서 await한 Promise가 reject되면 즉시 catch 블록으로 넘어가기 때문에, 동기 코드와 동일한 감각으로 에러를 처리할 수 있다는 점이 큰 장점입니다.
5. 여러 비동기 작업 병렬로 처리하기
5-1. 순차 처리의 함정
아래 코드처럼 서로 관련 없는 작업을 await로 하나씩 순서대로 처리하면 불필요하게 시간이 오래 걸립니다.
async function badExample() {
const user = await getUser(); // 1초 소요
const posts = await getPosts(); // 1초 소요
const comments = await getComments(); // 1초 소요
// 총 소요 시간: 약 3초
}세 작업이 서로 의존 관계가 없다면, 굳이 순서대로 기다릴 필요가 없습니다.
5-2. Promise.all로 병렬 처리하기
이럴 때는 Promise.all()을 사용해 여러 작업을 동시에 시작하고, 모두 끝날 때까지 한 번에 기다리는 것이 효율적입니다.
async function goodExample() {
const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
]);
// 총 소요 시간: 약 1초 (가장 오래 걸리는 작업 기준)
console.log(user, posts, comments);
}세 요청이 동시에 시작되기 때문에, 전체 소요 시간이 가장 오래 걸리는 작업 하나의 시간으로 줄어듭니다.
참고:
Promise.all()은 하나라도 실패(reject)하면 즉시 전체가 실패로 처리됩니다. 일부가 실패해도 나머지 결과를 모두 받고 싶다면Promise.allSettled()를 사용하면 됩니다.
6. 실전 예제: API 데이터 가져오기
실제 프로젝트에서 자주 쓰이는 패턴을 정리한 예제입니다.
async function fetchUserProfile(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP 에러: ${response.status}`);
}
const profile = await response.json();
return profile;
} catch (error) {
if (error instanceof TypeError) {
console.error("네트워크 연결을 확인해주세요.");
} else {
console.error("프로필 조회 실패:", error.message);
}
throw error; // 필요하다면 호출한 쪽으로 에러를 다시 전달
}
}
// 사용하는 쪽
async function displayProfile() {
try {
const profile = await fetchUserProfile(123);
console.log("프로필:", profile);
} catch (error) {
console.log("프로필을 표시할 수 없습니다.");
}
}7. 자주 하는 실수와 주의사항
1) forEach 안에서 await를 사용하는 경우
forEach는 async/await와 함께 사용해도 기다려주지 않습니다.
// 잘못된 예: 순서를 보장하지 않고 기다리지도 않음
items.forEach(async (item) => {
await processItem(item);
});
// 올바른 예 1: for...of 사용 (순차 처리)
for (const item of items) {
await processItem(item);
}
// 올바른 예 2: Promise.all + map 사용 (병렬 처리)
await Promise.all(items.map((item) => processItem(item)));2) 최상위 레벨에서 await 사용
모던 모듈 환경(ES 모듈)에서는 top-level await가 가능하지만, 일반 스크립트나 함수 밖에서는 async 함수로 감싸야 합니다.
// async 함수로 감싸서 실행
(async () => {
const data = await fetchData();
console.log(data);
})();3) try/catch를 생략하지 않기
await한 Promise가 실패했을 때 try/catch가 없으면 처리되지 않은 에러(unhandled rejection)로 이어질 수 있으므로, 실패 가능성이 있는 비동기 작업은 항상 에러 처리를 함께 고려하는 것이 좋습니다.
8. 정리
| 개념 | 설명 |
async |
함수를 비동기 함수로 선언, 항상 Promise 반환 |
await |
Promise가 처리될 때까지 대기, async 함수 내부에서만 사용 가능 |
try/catch |
비동기 작업의 에러를 동기 코드처럼 처리 |
Promise.all |
여러 비동기 작업을 병렬로 처리해 시간 단축 |
async/await는 복잡한 비동기 로직을 간결하고 읽기 쉬운 코드로 바꿔주는 강력한 도구입니다. 콜백 지옥이나 복잡한 .then() 체이닝 대신, 마치 동기 코드를 작성하듯 자연스러운 흐름으로 비동기 처리를 구현할 수 있다는 것이 가장 큰 장점입니다. 다만 병렬로 처리 가능한 작업까지 무조건 순차적으로 await하지 않도록 주의하고, 상황에 맞게 Promise.all 같은 도구도 함께 활용하는 것이 좋은 비동기 코드를 작성하는 핵심입니다.
'IT > Javascript' 카테고리의 다른 글
| JavaScript Object.freeze 총정리: 얕은 동결의 함정과 deepFreeze 구현법 (1) | 2026.08.08 |
|---|---|
| [Javascript] "use strict"란? — 자바스크립트 엄격 모드 완전 정리 (0) | 2026.08.08 |
| [Javascript] HTML에서 외부 js 파일의 함수 호출하기 (0) | 2025.02.27 |
| [Javascript] 삼항연산자 사용법 및 활용 예제, 중첩 삼항연산자 (0) | 2025.02.26 |
| [Javascript] attribute 추가, 수정, 조회, 삭제, 찾기, 목록 조회 (0) | 2025.02.26 |