fetch 뒤 두 번째 then에서 데이터가 undefined라면 네트워크보다 반환문을 먼저 보자. Promise 체인은 각 단계가 반환한 값을 다음 단계에 전달한다.
then 안에서 Promise를 반환해야 다음 단계가 기다린다
가장 작은 예로 5를 받아 1을 더해 보자.
Promise.resolve(5)
.then((value) => { return value + 1; })
.then((value) => console.log(value)); // 6첫 then의 return을 지우면 마지막 값은 undefined가 된다. 숫자 연산도 값 전달 규칙은 네트워크 응답과 같다.
const fakeResponse = new Response(JSON.stringify([{ id: 1, title: "읽기" }]));
Promise.resolve(fakeResponse)
.then((response) => {
return response.json();
})
.then((todos) => {
console.log(todos[0].title); // 읽기
})
.catch((error) => console.error(error));실제 서버 없이도 브라우저 콘솔에서 결과를 확인할 수 있는 예다. response.json()도 Promise를 반환한다. 앞 단계에서 return하면 다음 then은 JSON 변환이 끝난 값을 받는다. return을 빼면 콜백은 undefined를 반환하고, 다음 단계는 기다릴 작업과 데이터를 잃는다. return을 지워 다시 실행하면 다음 단계의 todos[0]에서 오류가 나는지 비교해 보자.
중괄호가 return 누락을 부른다
한 줄 화살표 함수는 표현식이 자동 반환되지만 중괄호를 쓰면 명시적으로 반환해야 한다.
const response = new Response("{}");
// 중괄호가 없으면 표현식이 자동으로 반환된다.
Promise.resolve(response).then((value) => value.json());
// 중괄호를 쓰면 return을 직접 적는다.
Promise.resolve(response).then((value) => { return value.json(); });형식보다 중요한 것은 각 단계가 어떤 값을 넘기는지 읽을 수 있는 것이다. 데이터 변환, 화면 렌더링, 오류 처리를 한 then에 모두 넣으면 return 누락도 찾기 어려워진다.
async/await도 같은 원리를 따른다
async function loadTodos() {
const response = await fetch("/api/todos");
if (!response.ok) throw new Error("요청 실패");
return response.json();
}await는 흐름을 읽기 쉽게 만들지만 실패 처리를 없애지는 않는다. HTTP 404나 500은 fetch 자체가 항상 거부하는 오류가 아니므로 response.ok를 확인해야 한다. 취소가 필요한 화면에서는 오래된 요청이 새 화면을 덮어쓰지 않도록 AbortController 같은 수명 관리도 고려한다.
then의 return 누락을 네트워크 없이 재현한다
Promise.resolve({ value: 3 })
.then((result) => { result.value * 2; })
.then((value) => console.assert(value === undefined));
Promise.resolve({ value: 3 })
.then((result) => { return result.value * 2; })
.then((value) => console.assert(value === 6));첫 체인은 계산은 했지만 값을 반환하지 않아 다음 단계가 undefined를 받는다. 실제 요청에서는 HTTP 상태 확인과 JSON 파싱 실패를 별도로 테스트해야 한다. 체인 중간에 로그만 추가했다면 반환값이 사라지지 않았는지 확인하자.
핵심 요약
Promise 체인은 각 then의 return을 다음 단계로 연결한다. response.json()처럼 비동기 결과도 반드시 반환해야 다음 then이 값을 받는다. 네트워크 처리에서는 response.ok 확인과 오류 처리를 분리하고, 체인이 길어지면 async/await로 의도를 더 명확히 하자.

