기다림의 미학: 비동기 처리와 Promise, async/await
서버에서 데이터를 받아올 때 화면이 멈추지 않도록 하는 비동기 프로그래밍의 원리와 깔끔한 작성법을 학습합니다.
우리가 스마트폰으로 배달 앱을 열어 음식점 목록을 볼 때, 그 데이터들은 우리 핸드폰 안에 저장되어 있는 것이 아닙니다. 멀리 떨어진 회사 서버에 "음식점 목록 좀 보내줘!"라고 요청을 보내고, 그 응답이 도착할 때까지 기다렸다가 화면에 보여주는 것입니다. 만약 이 데이터를 받아올 때까지 핸드폰 화면이 완전히 얼어버려서 터치조차 되지 않는다면 어떨까요? 사용자는 답답해서 앱을 지워버릴 것입니다. 프론트엔드 개발에서는 이처럼 '시간이 오래 걸리는 작업'을 처리하면서도 화면이 멈추지 않게 만드는 기술이 필수적입니다. 이번 에피소드에서는 AI가 생성한 통신 코드에서 가장 자주 마주치게 될 핵심 개념인 비동기(Asynchronous)의 원리와, 이를 세련되게 다루는 도구인 `Promise`와 `async/await`에 대해 완벽하게 이해해 보겠습니다.
프로그램의 흐름을 멈추지 않는 마법
기본적으로 컴퓨터 프로그램은 위에서부터 아래로 한 줄씩 차례대로 코드를 실행합니다. 앞의 명령이 끝나기 전에는 절대로 다음 명령으로 넘어가지 않습니다. 이러한 처리 방식을 프로그래밍 용어로 동기(Synchronous) 처리라고 부릅니다. 은행 창구에 한 줄로 서서 순서대로 업무를 보는 것과 똑같습니다.
멈추지 않는 화면의 비밀, 비동기 (Asynchronous)
하지만 네트워크 통신처럼 시간이 오래 걸리는 작업(수십 밀리초에서 수 초까지)을 동기 방식으로 처리하면 큰 문제가 생깁니다. 데이터를 가져오는 3초 동안 사용자는 스크롤도 내리지 못하고 버튼도 누르지 못하는 '렉 걸린' 상태를 겪게 됩니다.
이를 해결하기 위해 자바스크립트는 비동기(Asynchronous)라는 방식을 도입했습니다. 커피숍에서 진동벨을 받는 상황을 떠올려보세요. 주문을 한 뒤 커피가 나올 때까지 계산대 앞에서 멍하니 기다리는 것이 아니라, 진동벨을 받고 자리에 가서 친구와 수다를 떨거나 핸드폰을 봅니다. 그러다 진동벨이 울리면 커피를 가지러 가는 방식입니다.
자바스크립트도 마찬가지입니다. 서버에 데이터를 요청(주문)해 놓고, 데이터가 오기를 가만히 기다리는 대신 다음 코드를 먼저 실행(다른 볼일 보기)해 버립니다. 그러다 데이터가 도착하면 그때 가서 나머지 작업을 마저 처리하는 것입니다. 덕분에 사용자는 데이터를 기다리는 동안에도 화면을 자유롭게 조작할 수 있습니다.
동기(Sync) vs 비동기(Async) 처리 방식의 차이
| 비교 항목 | 동기 (Synchronous) | 비동기 (Asynchronous) |
|---|---|---|
| 실행 흐름 | 앞의 작업이 완전히 끝나야 다음 작업 시작 | 앞 작업이 안 끝났어도 다음 작업 먼저 시작 |
| 대기 시간 동안 | 프로그램 전체가 멈춤 (화면 멈춤 현상 발생) | 다른 작업 동시 수행 가능 (화면 조작 가능) |
| 현실 비유 | 은행 창구에서 순서대로 업무 보기 | 진동벨 받고 자리에서 할 일 하다가 알림 받기 |
| 프론트엔드 활용 | 단순한 계산, 변수 할당 등 즉각적인 작업 | 서버 데이터 요청, 타이머, 파일 읽기 등 지연 작업 |
약속의 영수증: Promise
비동기 방식으로 작업을 처리하려면 커피숍의 '진동벨' 역할을 해줄 장치가 필요합니다. 자바스크립트에서는 이 진동벨의 역할을 `Promise`(프로미스)라는 객체가 담당합니다. 이름 그대로 "나중에 꼭 데이터를 줄게!"라고 하는 '약속의 영수증'입니다.
Promise의 3가지 상태 변화
AI가 작성한 코드에서 데이터를 가져오는 함수를 보면, 그 결과값으로 데이터 자체가 아니라 `Promise` 객체를 돌려주는 것을 볼 수 있습니다. 이 영수증(Promise)은 시간이 흐름에 따라 세 가지 상태 중 하나로 변합니다.
- 대기(Pending): 주문을 막 넣고 진동벨이 울리기를 기다리는 상태입니다. 아직 데이터가 도착하지 않았습니다.
- 성공(Fulfilled): 데이터가 무사히 도착한 상태입니다. 진동벨이 울렸고 커피를 성공적으로 받았습니다.
- 실패(Rejected): 서버가 다운되었거나 인터넷이 끊겨 데이터를 받아오지 못한 상태입니다. 커피 머신이 고장 나서 환불을 받아야 하는 상황입니다.
Promise의 생애 주기 흐름
대기 (Pending)
데이터 요청 직후
서버 응답 기다림
대기 (Pending)
요청이 성공하면
성공 (Fulfilled)
대기 (Pending)
오류가 발생하면
실패 (Rejected)
우아하게 기다리는 법: async / await
초기 자바스크립트에서는 이 Promise 영수증을 다루기 위해 `.then()`과 `.catch()`라는 문법을 사용했습니다. 하지만 이 방식은 코드가 길어지면 꼬리에 꼬리를 무는 복잡한 형태가 되어 코드를 읽기 매우 어렵게 만들었습니다. 그래서 등장한 구원자가 바로 현대 프론트엔드 개발의 필수 문법인 `async / await`입니다.
코드를 다시 쉽게 읽히게 만드는 기적
`async`와 `await`는 비동기 코드를 마치 동기 코드처럼 편안하게 위에서 아래로 읽을 수 있게 만들어주는 마법의 키워드입니다. 사용법은 아주 직관적입니다.
- `async`: 이 함수가 비동기 작업을 포함하고 있다고 선언하는 마크입니다. 함수 이름표 앞에 붙여줍니다.
- `await`: 비동기 작업 앞에 붙여서 "이 Promise 영수증이 진짜 데이터로 바뀔 때까지 여기서 잠깐만 기다려!"라고 지시하는 키워드입니다.
async / await 코드의 흐름 읽기
AI가 작성한 일반적인 데이터 가져오기 함수는 다음과 같이 생겼습니다.
// async 키워드로 비동기 함수임을 선언
const fetchUserData = async () => {
// await: 서버에 데이터를 요청하고, 진짜 응답이 올 때까지 여기서 잠시 대기!
const response = await fetch('https://api.example.com/user');
// 위 작업이 끝나야 아래 코드가 실행됨
const data = await response.json();
return data;
}이렇게 `await`를 붙여주면, 컴퓨터는 그 줄에서 데이터가 올 때까지 기다려줍니다. 그렇다고 화면이 멈추는 동기 방식으로 돌아간 것은 아닙니다. 오직 저 함수 내부의 실행만 잠시 멈춰둘 뿐, 프로그램의 다른 부분(화면 애니메이션 등)은 여전히 쌩쌩하게 돌아가고 있는 완벽한 비동기 상태를 유지합니다.
바이브 코딩 시 자주 마주치는 비동기 실수
오해 또는 실수
await를 빼먹는 경우
바로잡기
서버 응답이 오기도 전에 다음 코드가 실행되어, 변수에 데이터 대신 쓸모없는 'Pending 상태의 Promise 영수증'이 통째로 들어가 버립니다. 빈 화면이나 에러가 발생합니다.
오해 또는 실수
async 없는 함수에서 await 사용
바로잡기
await 키워드는 반드시 async가 붙은 함수 내부에서만 사용할 수 있습니다. 바깥에서 쓰면 문법 에러가 발생하여 화면이 뻗어버립니다.
오해 또는 실수
에러 대비(try-catch) 누락
바로잡기
서버 요청은 실패(Rejected)할 수 있습니다. AI가 코드를 짤 때 실패 상황을 대비하는 방어 코드를 빼먹지 않았는지 항상 확인해야 합니다.
개념 퀴즈
퀴즈 답을 맞춰야 학습 완료가 됩니다.다음 중 자바스크립트의 비동기 처리 및 async/await 문법에 대한 설명으로 가장 올바른 것은 무엇입니까?
3줄 요약
- 1네트워크 요청처럼 오래 걸리는 작업 시 화면이 멈추지 않도록 다른 작업을 동시에 진행하는 비동기(Asynchronous) 처리가 필수적입니다.
- 2Promise는 나중에 데이터를 주겠다는 '약속 영수증'으로, 대기, 성공, 실패의 3가지 상태를 가집니다.
- 3async/await 문법을 사용하면 복잡한 비동기 코드를 위에서 아래로 자연스럽게 읽히는 동기 코드처럼 직관적이고 깔끔하게 작성할 수 있습니다.