우당탕탕
JavaScript async await 쓰면서 제가 실수한 패턴 5가지와 해결법 본문
JavaScript에서 async await를 쓰다가 생각보다 자주 삽질을 했어요. 특히 비동기 처리를 제대로 이해하지 못해서 기능 이상이 생길 때가 많았거든요. 이 글을 쓰는 이유도 실제로 제가 겪은 실수와 해결법을 공유해서 여러분이 같은 문제에 빠지지 않길 바라는 마음에서입니다.
오늘은 제가 자주 빠진 async await의 실수 패턴 5가지를 보여드리고, 각각 어떻게 해결했는지 차근차근 설명할게요. async await를 처음 쓰는 분은 물론 이미 사용해본 분들도 한 번씩 겪는 문제라 꽤 도움이 될 거예요.
개발 환경 / 버전 정보
제가 쓰던 환경은 Node.js 18 버전이고, 프론트엔드는 React 18를 사용했어요. async await는 ES2017부터 공식 지원하는 문법인데, 대부분 최신 환경에서는 무리 없이 돌아가니까 실제 개발환경에 맞게 적용하면 됩니다.
1. async 함수 안에서 await 안 쓴 실수, "그냥 함수인데 왜 안 기다려?"
사실 이 부분이 제일 기본인데도 처음엔 많이 헷갈렸거든요. async 함수 안에서 어떤 작업이 비동기임에도 await를 안 붙이면, 함수가 끝나기도 전에 다음 코드로 넘어가 버려요.
async function fetchUser() {
fetch('https://api.example.com/user'); // await가 없으면 프로미스가 완료되기 전에 호출 끝남
console.log('fetchUser 함수 끝');
}
fetchUser();
// 'fetchUser 함수 끝'이 먼저 찍히고, fetch 응답은 나중에 처리될 수 있음
이때는 꼭 await fetch(...)로 써야 fetch가 끝날 때까지 기다려 주는 거예요.
2. await만 쓰면 순차 실행, 병렬 처리하려다 시간 낭비한 경험
그런데 여기서 많이들 헷갈려 하는 게, 여러 비동기 작업을 동시에 하고 싶은데 무작정 await를 붙이면 전부 순서대로 실행된다는 거예요. 저도 이거 모르고 서버 요청 3개를 차례로 기다리다가 3배 늦게 끝난 적 있어요.
async function getData() {
const data1 = await fetch('https://api.example.com/data1');
const data2 = await fetch('https://api.example.com/data2');
const data3 = await fetch('https://api.example.com/data3');
return [data1, data2, data3];
}
// 이 코드는 data1이 끝나야 data2가 시작되고, 그 다음에 data3이 시작되니 시간이 그만큼 늘어남
병렬로 돌리려면 Promise.all을 써야 하는데, 저는 처음에 이 부분에서 엄청 막혔어요.
async function getDataParallel() {
const promises = [
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3'),
];
const results = await Promise.all(promises);
return results;
}
// 이렇게 하면 3개 요청이 병렬로 실행되어서 더 빨리 끝남
3. try-catch 안에 async 함수 호출 안 하고 await 안 붙여서 에러 안 잡힘
많이들 헷갈려하시는 게, async await 쓸 때는 에러 처리가 잘되게 try-catch를 쓴다는 점인데, 만약 await 없이 에러가 날 수 있는 async 함수를 호출하면 try 밖으로 에러가 나가 버려요. 저는 이거 때문에 한참 버그 찾았어요.
async function getUser() {
throw new Error('사용자 정보를 못 불러왔어요');
}
async function main() {
try {
getUser(); // await 없으면 에러가 여기서 잡히지 않음
} catch (e) {
console.error('에러 잡힘:', e);
}
}
main();
// 에러가 콘솔에 찍히긴 하지만 catch에서는 못 잡음
이 때는 무조건 await getUser()로 써야 에러가 try-catch 안에서 잘 잡힙니다.
4. async await로 감쌌는데 프로미스 체인과 함수 반환 실수
async 함수는 항상 프로미스를 리턴하는데, 가끔 async 함수 안에서 또 프로미스를 직접 반환하거나 중첩해서 헷갈리는 경우가 있더라고요. 저는 이 부분 이해하다가 무한대기 상태에 빠진 적이 있어요.
async function doubleAsync() {
return new Promise(resolve => {
setTimeout(() => {
resolve('완료');
}, 1000);
});
}
async function wrapper() {
return doubleAsync(); // 이렇게 하면 wrapper()도 프로미스를 반환
}
wrapper().then(console.log); // 1초 뒤에 '완료' 출력
async 함수는 내부에서 프로미스 리턴해도 결국 프로미스를 다시 한 번 감싸서 반환하는데, 이 부분은 크게 신경 안 써도 되지만 프로미스 체인 쓸 때 헷갈릴 수 있어요.
5. await 앞에 함수 호출을 깜빡하고 의도치 않은 순서로 처리된 경우
가장 황당했던 실수인데요, await를 붙여야 할 함수 호출 맨 앞에 빠뜨리고 그냥 쓰는 실수가 있었어요. 결과는 코드가 예상과 다르게 먼저 다음 줄부터 실행돼서 버그가 나더라고요.
async function fetchNumber() {
return 42;
}
async function test() {
const result = await fetchNumber; // () 괄호 빠져서 함수가 호출되지 않음
console.log(result); // Promise 객체 출력, 예상과 다름
}
test();
이렇게 쓰면 함수가 호출되지 않고 함수 자체가 Promise로 인식되니까 꼭 확인해야 해요.
심화: async await 잘 쓰려면 Promise 특성을 꼭 이해하세요
한 가지 더 팁을 드리자면, async await는 Promise 문법을 감싼 문법 그 자체라서 Promise의 동작 원리를 정확히 알아야 문제 없이 쓸 수 있어요. Promise 상태, then/catch와의 차이, 그리고 이벤트 루프 처리 구조를 이해하면 디버깅할 때 훨씬 수월해집니다.
자주 물어보시는 것들
Q. async 함수 내부에서 await 없이 프로미스를 그냥 반환해도 괜찮나요?
A. 네, async 함수는 언제나 프로미스를 반환하기 때문에, await 없이 프로미스를 반환해도 동작은 하지만 호출한 곳에서 명확히 기다려주고 싶으면 await를 붙이는 게 좋습니다.
Q. Promise.all에서 하나가 실패하면 어떻게 되나요?
A. Promise.all은 참여한 프로미스 중 하나라도 reject되면 즉시 전체가 reject됩니다. 실패해도 각각 성공한 결과를 다 보고 싶으면 Promise.allSettled를 사용하세요.
제가 async await 쓰다 자주 겪은 실수와 그 해결책, 그리고 심화 팁까지 정리해 봤는데요. 이걸 잘 이해하면 프론트엔드나 앱 개발에서 비동기 작업이 훨씬 편해질 거예요. 다음엔 Promise 체인과 async await 조합해서 쓰는 패턴도 다뤄볼게요.
'언어 > JavaScript' 카테고리의 다른 글
| React useEffect 의존성 배열 때문에 고생하다가 알게 된 핵심 포인트 (0) | 2026.05.17 |
|---|---|
| Next.js 13 App Router 마이그레이션 직접 해보니 생각보다 이랬어요 (1) | 2026.05.16 |
| TypeScript 처음 도입하면서 헷갈렸던 부분들 정리!! (0) | 2026.05.11 |
| [Js] 뒤로가기 버튼에서 발생하는 캐시 BF캐시 (bfcache) (0) | 2022.09.19 |
| [Js] 따옴표(quotes)와 백틱(backtick)의 차이 (0) | 2022.09.08 |
