Fetch API 심화 (Advanced Fetch API)

Fetch API 심화 (Advanced Fetch API)

자바스크립트에서 네트워크 요청을 다룰 때 Fetch API는 강력하고 유연한 도구다. 단순한 GET 요청부터 복잡한 설정까지, 다양한 상황에 맞춰 활용할 수 있다. 이번에는 Fetch API의 기본을 넘어 심화된 활용법을 코드와 함께 자세히 풀어보려고 한다.


Fetch API를 깊이 이해하면 요청 처리와 응답 관리를 훨씬 효율적으로 할 수 있다. 단계별로 차근차근 알아보자.


Fetch API 기본 복습

fetch는 Promise를 반환하며, 간단한 요청을 이렇게 처리할 수 있다:

fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then((response) => response.json())
    .then((data) => console.log(data.title))
    .catch((error) => console.log(error));
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

async/await를 사용하면 더 깔끔해진다:

async function getPost() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data.title);
}

getPost();
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

이제 여기서 더 나아가 보자.


1. 요청 옵션 커스터마이징

fetch는 두 번째 인자로 옵션 객체를 받아 요청을 세밀하게 조정할 수 있다:

async function createPost() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            title: "새 글",
            body: "내용입니다",
            userId: 1
        })
    });
    const data = await response.json();
    console.log(data);
}

createPost();
// { id: 101, title: "새 글", body: "내용입니다", userId: 1 }

POST 요청에 헤더와 본문을 추가해서 데이터를 전송했다.


2. 응답 상태 확인과 에러 처리

fetch는 404나 500 같은 오류 상태에서도 Promise를 거부하지 않으니, 상태를 직접 확인해야 한다:

async function fetchWithError(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`HTTP 오류: ${response.status}`);
    }
    return response.json();
}

async function run() {
    try {
        const data = await fetchWithError("https://jsonplaceholder.typicode.com/posts/999");
        console.log(data);
    } catch (error) {
        console.log(error.message);
    }
}

run();
// "HTTP 오류: 404"

response.ok로 성공 여부를 확인하고, 실패 시 에러를 던졌다.


3. AbortController로 요청 중단

AbortController를 사용하면 요청을 중간에 취소할 수 있다:

async function fetchWithAbort() {
    const controller = new AbortController();
    const signal = controller.signal;

    setTimeout(() => controller.abort(), 1000);

    try {
        const response = await fetch(
            "https://jsonplaceholder.typicode.com/posts",
            { signal }
        );
        const data = await response.json();
        console.log(data);
    } catch (error) {
        if (error.name === "AbortError") {
            console.log("요청이 중단됨");
        } else {
            console.log(error.message);
        }
    }
}

fetchWithAbort();
// 1초 후 "요청이 중단됨"

1초 후 요청을 중단해서 불필요한 네트워크 작업을 막았다.


4. 스트림으로 응답 처리

Response.body를 사용하면 데이터를 스트림으로 받아올 수 있다:

async function streamResponse() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let result = "";

    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        result += decoder.decode(value);
    }

    const data = JSON.parse(result);
    console.log(data[0].title);
}

streamResponse();
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

대용량 데이터를 조각 단위로 처리해서 메모리를 효율적으로 사용했다.


5. 파일 업로드 구현

FormData를 활용해서 파일을 업로드할 수 있다:

async function uploadFile() {
    const formData = new FormData();
    const file = new File(["테스트 데이터"], "test.txt", {
        type: "text/plain"
    });
    formData.append("file", file);
    formData.append("userId", "1");

    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        body: formData
    });
    const data = await response.json();
    console.log(data);
}

uploadFile();
// { id: 101, ... } (서버 반영은 모의 데이터로 처리됨)

FormData로 파일과 추가 데이터를 함께 보냈다.


6. 캐싱과 요청 재사용

cache 옵션으로 캐싱 전략을 조정할 수 있다:

async function fetchWithCache() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
        cache: "force-cache" // 캐시 강제 사용
    });
    const data = await response.json();
    console.log(data.title);
}

fetchWithCache();
// 캐시가 있으면 네트워크 요청 없이 캐시에서 가져옴

force-cache로 캐시를 우선 사용하도록 설정했다.


7. 요청 재시도 로직 추가

실패 시 재시도하는 로직을 구현해보자:

async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP 오류: ${response.status}`);
            }
            return response.json();
        } catch (error) {
            if (i === retries - 1) throw error;
            console.log(`재시도 ${i + 1} 실패: ${error.message}`);
            await new Promise((r) => setTimeout(r, 1000));
        }
    }
}

async function run() {
    try {
        const data = await fetchWithRetry("https://jsonplaceholder.typicode.com/posts/999");
        console.log(data);
    } catch (error) {
        console.log("최종 실패: " + error.message);
    }
}

run();
// "최종 실패: HTTP 오류: 404" (3번 재시도 후)

실패 시 1초 대기 후 최대 3번 재시도하도록 했다.


8. 성능과 안정성에 미치는 영향

Fetch API의 심화 활용이 코드에 어떤 영향을 주는지 살펴보자:

- 성능: 스트림 처리와 캐싱으로 대용량 데이터나 반복 요청을 최적화할 수 있다.

- 안정성: 에러 처리, 중단, 재시도로 네트워크 불안정성을 줄인다.

옵션 설정AbortController를 활용하면 요청 흐름을 완벽히 제어할 수 있다.


마무리

Fetch API는 단순한 요청을 넘어 스트림 처리, 파일 업로드, 재시도 등 다양한 상황에서 유연하게 동작한다. 옵션과 기능을 잘 조합하면 네트워크 작업을 안정적이고 효율적으로 관리할 수 있다.


+ Recent posts