JS 5.4 · 5부. 비동기 프로그래밍

async와 await

비동기 코드를 위에서 아래로 읽히게 작성합니다.

예상 학습 시간 15분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 중급
개념 설명

위에서 아래로 읽히게 씁니다

앞 단원의 then 체인은 콜백보다 낫지만, 여전히 값을 함수 안에서만 쓸 수 있습니다. await 는 그것을 변수에 바로 담게 해 줍니다.

같은 일, 두 가지 모양
// then 으로
loadUser().then(user => {
	console.log(user.name);
});

// await 로
const user = await loadUser();
console.log(user.name);

규칙은 둘입니다. await 는 Promise 앞에 붙입니다. 그리고 await 를 쓰는 함수에는 async 를 붙입니다.

최소 예제 · 편집 가능

차례대로 기다리기

script.js
function delay(ms, value) {
	return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

async function run() {
	console.log("시작");

	const first = await delay(100, "첫째");
	console.log(first);

	const second = await delay(100, "둘째");
	console.log(second);

	return "끝";
}

const result = await run();
console.log(result);
출력
시작 첫째 둘째 끝

5.2 의 콜백 지옥과 견주어 보세요. 중첩이 사라지고 보통의 코드처럼 읽힙니다.

마지막 두 줄처럼 함수 밖에서도 await 를 쓸 수 있습니다. 이것을 최상위 await 라고 합니다.

상세 사용법

async 함수는 Promise 를 돌려줍니다

script.js
async function greet() {
	return "안녕";
}

console.log(greet());
console.log(await greet());

greet().then(value => console.log("then 으로도:", value));
출력
Promise {} 안녕 then 으로도: 안녕

async 를 붙이면 무엇을 돌려주든 Promise 로 감싸집니다. 그래서 then 으로도 받을 수 있고, 부르는 쪽에서 await 로도 받을 수 있습니다.

상세 사용법

실패는 try...catch 로

catch 를 이어 붙이는 대신, 보통의 코드에서 쓰던 try...catch 를 그대로 사용합니다.

script.js
async function mayFail(shouldFail) {
	if (shouldFail) {
		throw new Error("문제가 생겼습니다");
	}
	return "성공";
}

try {
	console.log(await mayFail(false));
	console.log(await mayFail(true));
	console.log("이 줄은 나오지 않습니다");
} catch (error) {
	console.log("잡았습니다:", error.message);
} finally {
	console.log("끝났습니다");
}
출력
성공 잡았습니다: 문제가 생겼습니다 끝났습니다

throw 를 만나면 try 의 남은 줄을 건너뛰고 catch 로 갑니다. 5.3 의 체인이 중간 then 을 건너뛴 것과 같습니다.

상세 사용법

기다리지 않아도 되는 것은 함께

await 를 줄줄이 적으면 차례로 기다립니다. 서로 상관없는 일이라면 함께 시작해야 빠릅니다.

script.js
function delay(ms, value) {
	return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

const start = Date.now();

// 차례로 기다립니다 — 400밀리초
const a = await delay(200, "가");
const b = await delay(200, "나");
console.log(a, b, Date.now() - start >= 400);

const start2 = Date.now();

// 함께 기다립니다 — 200밀리초
const [c, d] = await Promise.all([delay(200, "다"), delay(200, "라")]);
console.log(c, d, Date.now() - start2 < 400);
출력
가 나 true 다 라 true

Promise.all 의 결과를 3.7 의 구조 분해로 바로 나눠 받았습니다.

흔한 실수

처음에 자주 걸리는 것

1. await 를 빠뜨립니다

값 대신 Promise 가 담깁니다. 오류가 나지 않아 undefined 가 나올 때까지 모릅니다.

const user = loadUser();          // Promise 가 담깁니다
console.log(user.name);           // undefined

const user = await loadUser();    // 이렇게 적습니다
2. async 없이 await 를 씁니다

함수 안에서 await 를 쓰려면 그 함수에 async 가 있어야 합니다.

SyntaxError: await is only valid in async functions
3. 반복문에서 하나씩 기다립니다

서로 상관없는 요청을 for 안에서 하나씩 기다리면 개수만큼 시간이 곱해집니다. Promise.all 로 함께 시작하세요.

실습 문제

직접 해보기

1. then 을 await 로 바꾸기 난이도 하

아래 코드를 await 로 바꿔 보세요.

Promise.resolve(2) .then(n => n * 10) .then(n => console.log(n));
const n = await Promise.resolve(2); console.log(n * 10);
2. 실패해도 이어 가기 난이도 중

숫자 셋을 차례로 처리하되, 하나가 실패해도 나머지를 계속 처리하세요. 음수면 실패합니다.

반복문 안쪽try...catch 를 두면 그 회차만 걸러집니다.
async function square(n) { if (n < 0) throw new Error(`${n} 은 음수입니다`); return n * n; } for (const n of [2, -1, 4]) { try { console.log(await square(n)); } catch (error) { console.log("건너뜁니다:", error.message); } }
요약

이 단원의 정리

  • await 는 Promise 의 값을 변수에 바로 담습니다.
  • await 를 쓰는 함수에는 async 를 붙입니다.
  • async 함수는 무엇을 돌려주든 Promise 로 감쌉니다.
  • 실패는 try...catch 로 받습니다.
  • 서로 상관없는 일은 Promise.all 로 함께 시작합니다.