본문으로 건너뛰기

단원 연습 문제

자바스크립트 목차비동기문제 12개

11단원 '비동기'의 여섯 레슨에서 아홉 문제를 내고, 1~10단원에서 세 문제를 섞었습니다. 마지막 두 문제는 코드를 직접 짜 봅니다.

한 번에 다 풀 필요는 없습니다. 문제 1~10은 폰으로도 풀 수 있고(약 30~40분), 끝의 만들어 보기 둘은 컴퓨터에서 Node로 직접 돌려 봅니다. 이 단원을 건너뛰어도 되는지 보려면 문제 2, 4, 6, 8, 10 다섯 문제만 먼저 풀어 보세요(약 15분). 건너뛸지 볼 때는 힌트를 펴지 않고 풉니다. 다섯 문제를 모두 맞히면 12단원으로 넘어가고, 하나라도 헷갈리면 정답의 '다시 볼 레슨'을 먼저 봅니다. 파이썬을 배웠다면 파이썬과 달라지는 곳이 이 다섯 문제에 들어 있습니다(정답의 '파이썬에서는' 줄). 끝의 만들어 보기 둘은 골라서 풉니다.

끝의 두 문제는 프로그램을 직접 만들어 봅니다. 글만 읽고 넘어가지 말고, 컴퓨터에서 작업 폴더의 main.js에 옮겨 적고 터미널에서 node main.js로 돌려 보세요. 실행 결과는 모두 Node 26으로 돌린 것입니다.

문제 1

0밀리초 타이머를 걸고 아랫줄을 실행합니다. 출력되는 세 줄을 마지막 줄까지 쓰세요.

console.log(1);
setTimeout(() => {
  console.log(2);
}, 0);
console.log(3);
힌트

0밀리초라도 setTimeout에 넘긴 함수는 할 일 줄에 서며, 지금 도는 코드가 끝까지 다 돈 뒤에 실행됩니다(규칙 8).

정답
실행 결과
1
3
2
  • 1번 줄: 지금 도는 코드라 1이 먼저 찍힙니다.
  • 2~4번 줄: 화살표 함수를 할 일 줄 끝에 세우고 바로 다음 줄로 넘어갑니다.
  • 5번 줄: 지금 도는 코드의 3이 둘째 줄에 찍히고, 코드가 끝난 뒤 할 일 줄에서 꺼낸 2가 마지막에 찍힙니다.

다시 볼 레슨: 나중에 하기: setTimeout

문제 2

new Promise를 만들고 .then을 두 번 이어 붙입니다. 출력되는 세 줄을 순서대로 쓰세요.

const p = new Promise(
  (resolve) => {
    console.log('1. 상자 생성');
    resolve(10);
  },
);
p.then((n) => n * 3).then(
  (res) => {
    console.log('3. 결과:', res);
  },
);
console.log('2. 바깥 끝');
힌트

new Promise에 넘긴 함수는 지금 바로 실행되지만, .then에 넘긴 함수는 지금 도는 코드('2. 바깥 끝')가 끝난 뒤에 실행됩니다.

정답
실행 결과
1. 상자 생성
2. 바깥 끝
3. 결과: 30
  • 1~6번 줄: new Promise에 넘긴 함수가 그 자리에서 바로 실행되어 '1. 상자 생성'을 찍고 10으로 이행합니다.
  • 7~9번 줄: .then의 콜백은 Promise 줄에 예약되고, 10번 줄의 '2. 바깥 끝'이 먼저 찍힙니다.
  • 그다음 첫 번째 .then이 10 * 3인 30을 넘겨 두 번째 .then이 '3. 결과: 30'을 찍습니다.

파이썬에서는 일반 코드가 위에서 아래로 기다리며 실행되지만 — 자바스크립트의 .then 콜백은 Promise 줄에 맡겨져 바깥의 동기 코드가 끝난 뒤에 실행됩니다.

다시 볼 레슨: Promise: 나중에 올 값

문제 3

도장 카드의 add 메서드를 화살표 함수로 감싸 setTimeout에 넘깁니다. 출력되는 두 줄을 쓰세요.

class Card {
  constructor() {
    this.stamps = 1;
  }
  add() {
    this.stamps += 2;
    console.log(this.stamps);
  }
}
const c = new Card();
setTimeout(() => c.add(), 10);
console.log(c.stamps);
힌트

12번 줄의 console.log(c.stamps)⁠는 타이머가 돌기 전에 먼저 실행되고, 10밀리초 뒤 화살표 함수 안에서 c.add()가 점(.)과 함께 불립니다.

정답
실행 결과
1
3
  • 12번 줄: 지금 도는 코드에서 아직 add()가 불리기 전이므로 1이 먼저 찍힙니다.
  • 11번 줄: 10밀리초 뒤 화살표 함수가 c.add()를 점과 함께 불러 this가 c에 붙고, 1 + 2인 3이 찍힙니다.

다시 볼 레슨: this를 잃을 때 · 나중에 하기: setTimeout

문제 4

0밀리초 타이머와 Promise.resolve().then⁠이 함께 있습니다. 출력되는 네 줄을 순서대로 쓰세요.

setTimeout(() => {
  console.log('타이머');
}, 0);
Promise.resolve()
  .then(() => {
    console.log('then 1');
  })
  .then(() => {
    console.log('then 2');
  });
console.log('동기');
힌트

지금 도는 코드('동기')가 끝나면, 할 일 줄의 '타이머'보다 Promise 줄의 .then 사슬이 끝까지 먼저 비워집니다(규칙 8).

정답
실행 결과
동기
then 1
then 2
타이머
  • 11번 줄의 '동기'가 가장 먼저 찍힙니다.
  • 이어서 Promise 줄의 'then 1'과 그 뒤에 이어진 'then 2'가 먼저 모두 실행됩니다.
  • Promise 줄이 다 비워진 뒤에야 할 일 줄의 '타이머'가 마지막에 찍힙니다.

파이썬에는 기본 문법에 마이크로태스크(Promise 줄)와 타이머 줄의 구분이 없지만 — 자바스크립트는 지금 코드가 끝날 때마다 늘 Promise 줄을 할 일 줄보다 먼저 비웁니다.

다시 볼 레슨: 타이머와 Promise, 누가 먼저

문제 5

음수를 만나면 오류를 던지는 함수를 try / catch / finally 안에서 부릅니다. 출력되는 두 줄을 쓰세요.

function check(n) {
  if (n < 0) {
    throw new Error('음수 불가');
  }
  return n * 2;
}
try {
  console.log(check(-3));
  console.log(check(5));
} catch (e) {
  console.log(e.message);
} finally {
  console.log('종료');
}
힌트

8번 줄의 check(-3)에서 오류가 던져지는 순간 9번 줄은 건너뛰고 catch (e)로 이동하며, 마지막에 finally가 실행됩니다.

정답
실행 결과
음수 불가
종료
  • 8번 줄에서 check(-3)이 오류를 던져 9번 줄(check(5))은 건너뛰고 catch에서 음수 불가를 찍습니다.
  • 이어서 finally 블록의 종료가 찍힙니다.

다시 볼 레슨: try와 catch · throw로 오류 알리기

문제 6

async 함수 work()를 부르고 바깥 줄을 이어서 실행합니다. 출력되는 세 줄을 순서대로 쓰세요.

async function work() {
  console.log('A');
  const v =
    await Promise.resolve('B');
  console.log(v);
}
work();
console.log('C');
힌트

work() 안에서 await 앞의 'A'는 지금 바로 찍히고, await를 만나면 work 함수만 멈춘 채 바깥의 'C'가 먼저 실행됩니다.

정답
실행 결과
A
C
B
  • 7번 줄에서 work()를 부르면 2번 줄의 'A'가 바로 찍힙니다.
  • 3~4번 줄의 await에서 work 함수만 잠시 멈추고 바깥으로 돌아가 8번 줄의 'C'가 둘째 줄에 찍힙니다.
  • 바깥 코드가 끝난 뒤 work 함수가 다시 이어져 5번 줄의 'B'가 찍힙니다.

파이썬에서 time.sleep 같은 동기 대기를 쓰면 A → B → C 차례로 멈춰 기다리지만 — 자바스크립트의 await는 그 async 함수만 잠시 멈추고 바깥 코드(C)를 먼저 진행합니다.

다시 볼 레슨: async와 await

문제 7

async 함수 안에서 던진 오류를 await와 try / catch로 잡습니다. 출력되는 두 줄을 쓰세요.

async function take(qty) {
  if (qty <= 0) {
    throw new Error('수량 오류');
  }
  return qty * 1000;
}
try {
  console.log(await take(2));
  console.log(await take(0));
} catch (e) {
  console.log(e.message);
}
힌트

8번 줄의 await take(2)는 2000을 꺼내 찍고, 9번 줄의 await take(0)은 거부된 Promise를 오류로 꺼내 catch (e)로 넘깁니다.

정답
실행 결과
2000
수량 오류
  • await take(2)는 이행된 값 2000을 꺼내 첫 줄에 찍습니다.
  • await take(0)은 거부된 Promise를 오류로 던져 catch (e)에서 수량 오류를 찍습니다.

다시 볼 레슨: 기다리는 일의 오류

문제 8

상태 번호가 404인 응답 객체 res를 검사합니다. 출력되는 두 줄을 쓰세요.

const res = new Response(
  '없음',
  { status: 404 },
);
console.log(res.ok, res.status);
if (!res.ok) {
  console.log('다시 확인');
} else {
  console.log('정상 처리');
}
힌트

상태 번호가 404일 때 res.ok는 false이고 res.status는 404입니다.

정답
실행 결과
false 404
다시 확인
  • 404 응답은 오류로 멈추지 않고 res.ok가 false인 Response 객체로 옵니다.
  • 첫 줄에 false 404가 찍히고, !res.ok가 참이므로 둘째 줄에 다시 확인이 찍힙니다.

파이썬의 일부 라이브러나 다른 환경과 달리 — 자바스크립트 fetch는 404나 500 응답에서도 자동으로 예외를 던지지 않으므로 res.ok를 직접 확인해야 합니다.

다시 볼 레슨: fetch로 데이터 받아 오기

문제 9

두 Promise를 Promise.all로 함께 기다린 뒤 reduce로 합계를 구합니다. 출력되는 한 줄을 쓰세요.

const p1 =
  Promise.resolve(4500);
const p2 =
  Promise.resolve(2000);
const list = await Promise.all([
  p1,
  p2,
]);
const sum = list.reduce(
  (acc, cur) => acc + cur,
  0,
);
console.log(sum);
힌트

await Promise.all([p1, p2])⁠는 [ 4500, 2000 ] 배열을 돌려주고, reduce가 두 수를 더합니다.

정답
실행 결과
6500
  • list는 [ 4500, 2000 ] 배열이 되고, reduce가 0 + 4500 + 2000을 계산해 6500을 찍습니다.

다시 볼 레슨: fetch로 데이터 받아 오기 · reduce로 하나로 모으기

문제 10

fail()이 던진 '연결 실패' 오류를 잡아 '연결 실패' 한 줄만 찍혀야 하는데, 아래 코드는 catch가 잡지 못하고 멈췄습니다. 몇 번 줄을 어떻게 고쳐야 할까요? 한 줄만 고쳐, 고친 줄을 쓰세요.

async function fail() {
  throw new Error('연결 실패');
}
try {
  fail();
} catch (e) {
  console.log(e.message);
}
실행 결과
main.js:2  throw new Error('연결 실패');        ^ Error: 연결 실패    at fail (main.js:2:9)    at main.js:5:3
힌트

async 함수가 돌려준 거부된 Promise 상자를 try 안에서 오류로 꺼내려면 호출 앞에 무엇을 붙여야 하는지 보세요.

정답

5번 줄을 await fail();로 고칩니다.

async function fail() {
  throw new Error('연결 실패');
}
try {
  await fail();
} catch (e) {
  console.log(e.message);
}
실행 결과
연결 실패

5번 줄에서 await 없이 fail()만 부르면 거부된 Promise 상자만 만들어진 채 try 블록을 지나쳐 버립니다. await fail();로 기다려야 거부된 Promise가 그 자리에서 오류로 던져져 catch (e)가 잡습니다.

파이썬의 일반 함수 호출과 달리 — 자바스크립트의 async 함수는 늘 Promise를 돌려주므로 try / catch 안에서도 반드시 await를 붙여 기다려야 오류가 잡힙니다.

다시 볼 레슨: 기다리는 일의 오류

문제 11 (만들어 보기)

응답 객체 res를 받아 res.ok가 거짓이면 new Error('실패')를 던지고, 참이면 await res.json()으로 본문 객체를 꺼내 d.price를 돌려주는 async 함수 read(res)의 몸통을 채우세요. 이렇게 나와야 합니다.

4500
실패

첫 번째 시험 값의 4500을 3000으로 바꾸면 이렇게 나와야 합니다.

3000
실패

아래 뼈대에서 주석 둘 아래를 채우세요.

async function read(res) {
  // 1. !res.ok이면 '실패' 던지기

  // 2. JSON 꺼내 price 돌려주기
}

이어서 정상 응답과 404 응답을 넣어 확인합니다.

const ok = Response.json({
  price: 4500,
});
const bad = new Response(
  '없음',
  { status: 404 },
);
for (const r of [ok, bad]) {
  try {
    console.log(await read(r));
  } catch (e) {
    console.log(e.message);
  }
}
힌트

1번 주석 아래에 if (!res.ok)로 '실패' 오류를 던지고, 2번 주석 아래에 const d = await res.json();⁠과 return d.price;를 씁니다.

정답

모범 답입니다.

async function read(res) {
  if (!res.ok) {
    throw new Error('실패');
  }
  const d = await res.json();
  return d.price;
}

이어서 정상 응답과 404 응답을 차례로 넣어 확인합니다.

const ok = Response.json({
  price: 4500,
});
const bad = new Response(
  '없음',
  { status: 404 },
);
for (const r of [ok, bad]) {
  try {
    const p = await read(r);
    console.log(p);
  } catch (e) {
    console.log(e.message);
  }
}
실행 결과
4500
실패

다시 볼 레슨: fetch로 데이터 받아 오기 · 기다리는 일의 오류

문제 12 (만들어 보기)

이번에는 빈 데서 async 함수를 직접 씁니다. 두 Promise p1과 p2를 인자로 받아 Promise.all로 함께 기다린 뒤, 두 결과 숫자의 합계를 돌려주는 async 함수 both(p1, p2)를 작성하세요. 이렇게 나와야 합니다.

6500

시험 값을 Promise.resolve(1000)⁠과 Promise.resolve(2500)⁠으로 바꾸면 3500이 나와야 합니다.

3500

아래 칸의 위쪽에 both 함수를 쓰세요.

// async 함수 both 쓰기

const a =
  Promise.resolve(4500);
const b =
  Promise.resolve(2000);
const sum = await both(a, b);
console.log(sum);
힌트

async function both(p1, p2) 안에서 Promise.all([p1, p2]) 앞에 await를 붙여 [x, y]로 받은 뒤 return x + y;를 돌려줍니다.

정답

모범 답입니다.

async function both(p1, p2) {
  const list = [p1, p2];
  const [x, y] =
    await Promise.all(list);
  return x + y;
}
const a =
  Promise.resolve(4500);
const b =
  Promise.resolve(2000);
const sum = await both(a, b);
console.log(sum);
실행 결과
6500

다시 볼 레슨: fetch로 데이터 받아 오기 · async와 await


틀린 문제는 그 레슨으로 돌아가 모션 그림과 '자주 틀리는 곳'을 보고, 하루쯤 지난 뒤 그 문제만 다시 풀어 보세요. 그날 바로 다시 풀면 방금 본 정답이 기억나 맞힌 것인지 알기 어렵습니다.

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

자바스크립트 목차로