본문으로 건너뛰기

async와 await

자바스크립트 목차비동기레슨 61 / 76

목차

Promise: 나중에 올 값에서는 .then()을 이어 붙여 나중에 도착할 값을 받았습니다. 하지만 단계가 많아지면 .then() 안의 화살표 함수도 읽기 번거로워집니다. 함수 앞에 async를 붙이고 Promise 앞에 await를 쓰면, 기다리는 코드를 마치 위에서 아래로 흐르는 일반 코드처럼 읽히게 쓸 수 있습니다.

async 함수는 늘 Promise를 돌려준다

function 앞에 async를 붙이면, 그 함수 안에서 일반 값을 return하더라도 자동으로 이행된 Promise 상자에 감싸서 돌려줍니다.

async function getPrice() {
  return 4000;
}
const p = getPrice();
console.log(p);
p.then((v) => {
  console.log('꺼낸 값:', v);
});
실행 결과
Promise { 4000 }
꺼낸 값: 4000

getPrice()는 숫자 4000을 그대로 돌려주지 않고 Promise { 4000 } 상자를 돌려줍니다.

await는 그 async 함수만 잠시 멈춘다

async 함수 안에서 Promise 앞에 await를 붙이면, 그 Promise가 이행될 때까지 그 async 함수만 잠시 멈추고 상자 안의 결과 값을 꺼내 줍니다. 그동안 함수를 부른 바깥쪽 코드는 멈추지 않고 계속 실행됩니다.

async function brew() {
  console.log('내리기 시작');
  const p =
    Promise.resolve('커피');
  const coffee = await p;
  console.log('다 됨:', coffee);
}
brew();
console.log('다음 손님');
실행 결과
내리기 시작
다음 손님
다 됨: 커피

4번 줄의 await를 만났을 때 왜 '다 됨: 커피'보다 바깥의 '다음 손님'이 먼저 찍히는지 그림으로 따라가 보세요.

5단계 중 1단계. 6번 줄에서 `brew()`를 부르면 2번 줄의 `console.log("추출 시작")`까지는 일반 함수처럼 지금 도는 칸에서 바로 실행되어 실행 결과 칸에 `추출 시작`을 찍습니다.

  1. 7번 줄에서 brew()를 부르면, await를 만나기 전까지인 2번 줄 '내리기 시작'은 지금 바로 실행됩니다.
  2. 4번 줄에서 await를 만나는 순간 brew 함수만 잠시 멈춰 남은 5번 줄을 Promise 줄에 맡겨 두고, 실행 흐름은 brew()를 부른 바깥쪽으로 돌아갑니다.
  3. 바깥의 8번 줄 console.log('다음 손님')이 먼저 실행됩니다.
  4. 바깥의 지금 도는 코드가 끝난 뒤, Promise 줄에서 기다리던 brew의 5번 줄이 깨어나 다 됨: 커피를 찍습니다.

맨 위의 await

Node의 모듈 파일("type": "module")에서는 async 함수 바깥인 파일 맨 위에서도 await를 쓸 수 있습니다. 이때 맨 위의 await는 이 파일의 남은 아랫줄만 멈추며, 그동안 이미 걸어 둔 타이머는 정상적으로 돌아갑니다.

setTimeout(() => {
  console.log('타이머');
}, 10);
console.log('a');
await new Promise((r) => {
  setTimeout(r, 30);
});
console.log('b');
실행 결과
a
타이머
b

5~7번 줄의 맨 위 await가 30밀리초 동안 아래 8번 줄('b')을 멈춰 두는 동안, 10밀리초 타이머가 할 일 줄에서 꺼내져 '타이머'를 먼저 찍고, 30밀리초가 지나면 'b'가 찍힙니다. (한편 12단원에서 배울 웹 브라우저의 일반 스크립트 파일에서는 맨 위 await를 쓸 수 없어 async 함수 안에서만 await를 씁니다.)

자주 틀리는 곳

1. await가 프로그램 전체를 멈춘다고 생각함

await는 자신이 들어 있는 async 함수의 아랫줄만 잠시 미뤄 둘 뿐, 프로그램 전체를 얼리지 않습니다. 그래서 brew()를 부른 직후의 바깥 줄이나 다른 타이머는 기다리는 동안에도 계속 실행됩니다.

2. async 함수를 부르고 await 없이 값을 바로 쓰려 함

async 함수가 돌려주는 것은 값이 아니라 Promise 상자입니다. await 없이 변수에 담으면 상자(Promise { ... })가 담겨 덧셈 같은 계산을 바로 할 수 없습니다.

async function getTax() {
  return 400;
}
console.log(getTax());
const tax = await getTax();
console.log(tax + 4000);
실행 결과
Promise { 400 }
4400

3. async가 아닌 일반 함수 안에서 await를 씀

일반 함수 안에서 await를 쓰면 예약어 문법 오류(SyntaxError)가 납니다. 함수 안에서 await를 쓰려면 반드시 그 함수 선언 앞에 async를 붙여야 합니다.

function bad() {
  await Promise.resolve(1);
}
실행 결과
main.js:2  await Promise.resolve(1);  ^^^^^ SyntaxError: Unexpected reserved word

요약

  • async function: 함수가 돌려주는 값을 늘 Promise 상자에 감싸 돌려준다.
  • await: async 함수 안에서 Promise가 이행될 때까지 그 함수만 잠시 멈추고, 이행된 값을 상자에서 꺼내 준다.
  • await로 함수가 멈춘 동안 함수를 부른 바깥쪽 코드는 멈추지 않고 끝까지 실행된다.
  • 일반 함수 안에서는 await를 쓸 수 없으며, 반드시 async가 붙은 함수(또는 모듈 맨 위)에서 쓴다.

연습 문제

문제 1

다음 코드를 실행했을 때 출력되는 세 줄을 마지막 줄까지 순서대로 쓰세요.

async function cook() {
  console.log('1. 반죽');
  const p =
    Promise.resolve('쿠키');
  const item = await p;
  console.log('2. 완성:', item);
}
cook();
console.log('3. 포장 준비');
힌트

cook() 안에서 await를 만나기 전 줄은 바로 실행되고, await 아래 줄은 바깥의 '3. 포장 준비'가 끝난 뒤에 실행됩니다.

정답
실행 결과
1. 반죽
3. 포장 준비
2. 완성: 쿠키
  • cook()을 부르면 await 앞인 '1. 반죽'이 먼저 찍힙니다.
  • await에서 cook 함수가 잠시 멈추고 바깥으로 돌아와 '3. 포장 준비'가 둘째 줄에 찍힙니다.
  • 바깥 코드가 끝난 뒤 멈춰 있던 cook 함수가 다시 이어져 '2. 완성: 쿠키'가 마지막에 찍힙니다.

문제 2

다음 코드는 getPrice()에서 돌려준 4500에 500을 더해 5000을 찍으려 했지만 [object Promise]500이 찍혔습니다. 4번 줄 한 줄만 고쳐 5000이 찍히게 만드세요.

async function getPrice() {
  return 4500;
}
const p = getPrice();
console.log(p + 500);
힌트

async 함수가 돌려주는 Promise 상자에서 숫자 4500을 꺼내려면 호출 앞에 await를 붙여야 합니다.

정답
  • 고친 줄: const p = await getPrice();
async function getPrice() {
  return 4500;
}
const p = await getPrice();
console.log(p + 500);
실행 결과
5000
  • await getPrice()로 기다려야 상자 안의 숫자 4500이 꺼내져 4500 + 500인 5000이 찍힙니다.

문제 3

Promise.resolve(3000)⁠을 await로 기다려 변수 base에 담고, 거기에 500을 더한 값을 출력하는 async 함수 showTotal()을 작성해 실행하세요.

3500
// async 함수 showTotal 쓰기
// 아래에서 showTotal() 부르기
힌트

async function showTotal() 안에서 await로 3000을 꺼내 base에 담고 console.log(base + 500);⁠을 실행합니다.

정답
async function showTotal() {
  const p =
    Promise.resolve(3000);
  const base = await p;
  console.log(base + 500);
}
showTotal();
실행 결과
3500

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로