지금까지 배운 Promise, async / await, try / catch가 가장 많이 쓰이는 곳이 바로 네트워크로 서버에서 데이터를 받아 오는 fetch 함수입니다. 카페 메뉴판이나 오늘의 한마디를 서버에서 받아 올 때 왜 await를 두 번 쓰고, 왜 res.ok를 꼭 확인해야 하는지 알아봅니다.
두 번 기다리는 fetch: 응답 머리와 본문 JSON
fetch(주소)로 데이터를 받아 올 때는 await를 두 단계로 씁니다.
// 실제 서버 요청 모양 …
const u = 'https://example.com';
const res = await fetch(u);
console.log(res.ok, res.status);
const d = await res.json();
console.log(d.id, d.author);
true 200
1 Rumi
- 1단계 (
const res = await fetch(u)): 서버에서 응답 머리(상태 번호res.status와 성공 여부res.ok)가 담긴Response객체를 먼저 받습니다. - 2단계 (
const d = await res.json()): 응답 본문 글자를 끝까지 읽고JSON.parse처럼 자바스크립트 객체로 바꿀 때까지 한 번 더 기다립니다.
아래에서는 인터넷 연결 없이도 언제나 똑같이 돌려 볼 수 있도록, Node 내장 Response 객체로 서버 응답을 만들어 두 단계를 실습해 봅니다.
async function fakeFetch() {
return Response.json(
{ menu: '라테', p: 4500 },
{ status: 200 },
);
}
const res = await fakeFetch();
console.log(res.ok, res.status);
const d = await res.json();
console.log(d.menu, d.p);
true 200
라테 4500404 오류는 catch로 바로 가지 않는다
가장 주의할 점은 없는 주소(404)나 서버 내부 오류(500)가 와도 fetch는 오류를 던지지 않는다는 것입니다. 서버와 연결만 되었다면 fetch는 이행된 Response 객체를 돌려주며, 대신 res.ok가 false가 됩니다.
따라서 응답을 받으면 반드시 if (!res.ok)로 확인해 직접 throw new Error(...)를 던져야 합니다.
async function getMenu(code) {
const res = new Response(
'없음',
{ status: code },
);
if (!res.ok) {
throw new Error(
`HTTP ${res.status}`,
);
}
return await res.json();
}
이어서 404 상태 번호를 넣어 실행해 봅니다.
try {
await getMenu(404);
} catch (e) {
console.log(e.message);
}
HTTP 404반면 인터넷이 끊기거나 잘못된 도메인 주소라서 서버에 아예 닿지 못했을 때만 fetch 자체가 TypeError로 거부되어 곧바로 catch로 넘어갑니다.
여러 요청을 함께 기다리는 Promise.all
커피(200밀리초)와 쿠키(100밀리초)를 따로따로 await하면 시간이 합쳐져 300밀리초가 걸립니다. 서로 순서가 상관없는 여러 Promise를 동시에 출발시키고 모두 끝날 때까지 한 번에 기다릴 때는 Promise.all([ ... ])을 씁니다.
const p1 = new Promise((r) => {
setTimeout(r, 40, '커피');
});
const p2 = new Promise((r) => {
setTimeout(r, 10, '쿠키');
});
const arr = await Promise.all([
p1,
p2,
]);
console.log(arr.join(' '));
커피 쿠키'쿠키'가 10밀리초 만에 먼저 끝나더라도, Promise.all이 돌려주는 배열의 순서는 넣은 순서 그대로 ['커피', '쿠키']로 맞춰집니다. (한편 웹 브라우저에서 다른 도메인 주소로 fetch를 보낼 때는 그 서버가 허락 표시인 CORS 설정을 열어 두어야 응답을 읽을 수 있습니다.)
자주 틀리는 곳
1. res.json() 앞에 await를 빼먹음
res.json()도 본문을 읽는 비동기 메서드라 Promise를 돌려줍니다. 앞에 await를 빼먹으면 객체가 아니라 Promise 상자가 찍힙니다.
const res = Response.json({
qty: 2,
});
console.log(res.json());
Promise { <pending> }2. 같은 응답에서 res.json()을 두 번 부름
응답 본문은 한 번 읽으면 비워지는 흐름(스트림)이라, await res.json()을 두 번 부르면 두 번째에 TypeError: Body is unusable 오류가 납니다. 한 번 꺼낸 객체는 변수에 담아 두고 여러 번 쓰세요.
const res = Response.json({
qty: 2,
});
const a = await res.json();
try {
const b = await res.json();
} catch (err) {
console.log('오류:', err.name);
}
오류: TypeError요약
fetch의 두 단계:const res = await fetch(url);로 응답 머리를 받고,const data = await res.json();으로 본문 객체를 꺼낸다.res.ok확인:404나500상태 번호는catch로 바로 가지 않고res.ok === false로 오므로,if (!res.ok)일 때 직접 오류를 던져 걸러낸다.- 서버에 아예 연결하지 못했을 때만
fetch가 거부되어catch로 간다. Promise.all([p1, p2]): 여러Promise를 함께 기다려 넣은 순서대로 결과 배열을 받는다.
연습 문제
문제 1
다음 코드를 실행했을 때 출력되는 두 줄을 순서대로 쓰세요.
const res = new Response(
'없음',
{ status: 404 },
);
console.log(res.ok, res.status);
try {
if (!res.ok) {
throw new Error('찾지 못함');
}
console.log('통과');
} catch (e) {
console.log(e.message);
}
힌트
상태 번호가 404이면 res.ok는 false이고, if (!res.ok)가 참이 되어 오류를 던집니다.
정답
false 404
찾지 못함- 5번 줄에서
res.ok는false,res.status는404가 찍힙니다. - 7~8번 줄에서
if (!res.ok)가 참이라'찾지 못함'오류를 던지고, 12번 줄에서찾지 못함을 찍습니다.
문제 2
다음 코드는 응답 본문에서 price인 4000을 꺼내 찍으려 했지만 undefined가 찍혔습니다. 4번 줄 한 줄만 고쳐 4000이 찍히게 만드세요.
const res = Response.json({
price: 4000,
});
const d = res.json();
console.log(d.price);
힌트
res.json()은 Promise를 돌려주므로 앞에 await를 붙여야 객체를 꺼낼 수 있습니다.
정답
- 고친 줄:
const d = await res.json();
const res = Response.json({
price: 4000,
});
const d = await res.json();
console.log(d.price);4000문제 3
두 Promise p1(Promise.resolve(4000))과 p2(Promise.resolve(2000))를 Promise.all로 한꺼번에 기다려 배열 [a, b]로 받은 뒤, 두 값의 합계 6000을 출력하는 코드를 완성하세요.
6000
const p1 =
Promise.resolve(4000);
const p2 =
Promise.resolve(2000);
// Promise.all로 기다려 합계 찍기
힌트
Promise.all([p1, p2]) 앞에 await를 붙여 [a, b]로 받은 뒤 console.log(a + b);로 합계를 찍습니다.
정답
const p1 =
Promise.resolve(4000);
const p2 =
Promise.resolve(2000);
const [a, b] =
await Promise.all([
p1,
p2,
]);
console.log(a + b);6000