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가 두 수를 더합니다.
정답
6500list는[ 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
틀린 문제는 그 레슨으로 돌아가 모션 그림과 '자주 틀리는 곳'을 보고, 하루쯤 지난 뒤 그 문제만 다시 풀어 보세요. 그날 바로 다시 풀면 방금 본 정답이 기억나 맞힌 것인지 알기 어렵습니다.