Web Development

Fetch API: HTTP Requests Modern di JavaScript

Kuasai Fetch API untuk GET, POST, PUT, DELETE — headers, streaming, AbortController, error handling, dan pola HTTP modern

1. Pengenalan Fetch API

Fetch API adalah cara modern untuk melakukan HTTP requests di JavaScript. Fetch menggantikan XMLHttpRequest (XHR) yang sudah tua dan menyediakan API yang lebih bersih, berbasis Promises, dan lebih powerful.

Fetch vs XMLHttpRequest

Fitur Fetch API XMLHttpRequest
SyntaxModern, clean, Promise-basedCallback-based, verbose
Promises✅ Ya❌ Tidak (butuh wrapper)
Async/Await✅ Ya❌ Tidak langsung
Streaming✅ Ya (ReadableStream)❌ Terbatas
Request Cancellation✅ AbortController✅ .abort()
Progress Upload❌ Tidak (butuh XHR)✅ Ya
CORS✅ Lebih baik⚠️ Masalah di browser lama
JavaScript — Fetch Dasar
// ===== Fetch API Dasar =====
// fetch() mengembalikan Promise yang resolve ke Response object

// Contoh paling sederhana
fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));

// Dengan async/await (lebih direkomendasikan)
async function ambilData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

// ===== Perbedaan dengan XHR =====

// XMLHttpRequest (cara lama):
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function() {
    if (xhr.status === 200) {
        const data = JSON.parse(xhr.responseText);
        console.log(data);
    }
};
xhr.onerror = function() { console.error('Error'); };
xhr.send();

// Fetch (cara modern — lebih singkat!):
const data = await (await fetch('https://api.example.com/data')).json();
console.log(data);
💡 Fetch Selalu Resolve

Fetch hanya reject promise-nya saat network error (tidak ada internet, DNS failure). HTTP status seperti 404 atau 500 bukan error untuk fetch — promise tetap resolve. Anda harus mengecek response.ok secara manual.

2. GET Request

JavaScript — GET Requests
// ===== GET Request Sederhana =====
async function getUsers() {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const users = await response.json();
    console.log(`Ditemukan ${users.length} users`);
    return users;
}

// ===== GET dengan Query Parameters =====
async function searchUsers(query) {
    const params = new URLSearchParams({
        q: query,
        limit: 10,
        offset: 0
    });

    const url = `https://api.example.com/users?${params}`;
    const response = await fetch(url);
    return await response.json();
}

// Atau manual:
const url = new URL('https://api.example.com/users');
url.searchParams.append('q', 'beebane');
url.searchParams.append('page', '1');
console.log(url.toString());
// → https://api.example.com/users?q=beebane&page=1

// ===== GET dengan Headers =====
async function getProfile(userId) {
    const response = await fetch(`/api/users/${userId}`, {
        method: 'GET',
        headers: {
            'Authorization': 'Bearer eyJhbGciOiJIUzI1...',
            'Accept': 'application/json',
            'X-Custom-Header': 'custom-value'
        }
    });

    if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
}

// ===== Membaca Response Berbagai Format =====
async function ambilData(url) {
    const response = await fetch(url);

    // JSON
    const jsonData = await response.json();

    // Text
    // const textData = await response.text();

    // Binary (Blob)
    // const blobData = await response.blob();

    // ArrayBuffer (binary raw)
    // const bufferData = await response.arrayBuffer();

    // FormData
    // const formData = await response.formData();

    return jsonData;
}

3. POST, PUT, DELETE

JavaScript — POST Request
// ===== POST: Mengirim Data ke Server =====
async function createUser(userData) {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        },
        body: JSON.stringify({
            nama: userData.nama,
            email: userData.email,
            role: 'member'
        })
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || 'Gagal membuat user');
    }

    const newUser = await response.json();
    console.log('User dibuat:', newUser);
    return newUser;
}

// Penggunaan:
try {
    const user = await createUser({
        nama: 'Beebane',
        email: 'bee@example.com'
    });
    console.log('ID user baru:', user.id);
} catch (error) {
    console.error('Gagal:', error.message);
}

// ===== POST dengan Form Data =====
async function uploadFile(fileInput) {
    const formData = new FormData();
    formData.append('avatar', fileInput.files[0]);
    formData.append('nama', 'Beebane');
    formData.append('bio', 'Web Developer');

    // TIDAK perlu set Content-Type — browser otomatis set
    // dengan boundary yang benar untuk multipart/form-data
    const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
    });

    return await response.json();
}

// ===== POST dengan URL Encoded Form =====
async function login(username, password) {
    const params = new URLSearchParams();
    params.append('username', username);
    params.append('password', password);

    const response = await fetch('/api/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: params
    });

    return await response.json();
}
JavaScript — PUT, PATCH, DELETE
// ===== PUT: Update Seluruh Resource =====
async function updateUser(userId, userData) {
    const response = await fetch(`/api/users/${userId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
    });
    return await response.json();
}

// ===== PATCH: Update Sebagian Resource =====
async function patchUser(userId, changes) {
    const response = await fetch(`/api/users/${userId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(changes)
    });
    return await response.json();
}

// Hanya update email saja:
await patchUser(1, { email: 'baru@example.com' });

// ===== DELETE: Hapus Resource =====
async function deleteUser(userId) {
    const response = await fetch(`/api/users/${userId}`, {
        method: 'DELETE'
    });

    if (response.status === 204) {
        console.log('User berhasil dihapus');
        return true;
    }

    if (!response.ok) {
        throw new Error('Gagal menghapus user');
    }

    return true;
}

// ===== Menggunakan Semua HTTP Methods =====
const API = {
    baseUrl: 'https://jsonplaceholder.typicode.com',

    async get(path) {
        const res = await fetch(`${this.baseUrl}${path}`);
        return res.json();
    },

    async post(path, data) {
        const res = await fetch(`${this.baseUrl}${path}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return res.json();
    },

    async put(path, data) {
        const res = await fetch(`${this.baseUrl}${path}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return res.json();
    },

    async delete(path) {
        const res = await fetch(`${this.baseUrl}${path}`, {
            method: 'DELETE'
        });
        return res.ok;
    }
};

// Penggunaan:
const posts = await API.get('/posts');
const newPost = await API.post('/posts', { title: 'Halo', body: 'Isi' });
await API.put(`/posts/${newPost.id}`, { title: 'Updated' });
await API.delete(`/posts/${newPost.id}`);

4. Headers & Options

JavaScript — Headers Object
// ===== Headers API =====

// Membuat Headers dari object literal
const headers = new Headers({
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token-abc',
    'Accept': 'application/json',
    'X-Request-ID': crypto.randomUUID()
});

// Menambah header
headers.append('X-Custom', 'value');

// Membaca header
console.log(headers.get('Content-Type'));  // → 'application/json'

// Cek header
console.log(headers.has('Authorization')); // → true

// Hapus header
headers.delete('X-Custom');

// Iterasi headers
for (const [key, value] of headers) {
    console.log(`${key}: ${value}`);
}

// ===== Request Object (Alternatif) =====
// Bisa buat Request object terlebih dahulu
const request = new Request('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token-abc'
    },
    body: JSON.stringify({ key: 'value' }),
    mode: 'cors',              // 'cors', 'no-cors', 'same-origin'
    credentials: 'same-origin', // 'omit', 'same-origin', 'include'
    cache: 'no-cache',         // 'default', 'no-cache', 'reload', etc.
    redirect: 'follow',        // 'follow', 'error', 'manual'
    referrerPolicy: 'no-referrer',
    signal: null,              // AbortSignal
    keepalive: false           // Kirim meski halaman ditutup
});

const response = await fetch(request);

// ===== Complete Fetch Options =====
const fullOptions = {
    method: 'POST',
    headers: new Headers({
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
        'Cache-Control': 'no-cache',
        'X-API-Key': 'your-api-key'
    }),
    body: JSON.stringify({ action: 'update' }),
    mode: 'cors',
    credentials: 'include',
    cache: 'no-cache',
    redirect: 'follow',
    referrer: 'https://example.com/page',
    referrerPolicy: 'strict-origin-when-cross-origin',
    keepalive: false
};

const response2 = await fetch('/api/action', fullOptions);

5. Response Object

JavaScript — Response Object
// ===== Response Properties =====
const response = await fetch('https://api.example.com/data');

// Status
console.log(response.status);      // 200, 404, 500, dll
console.log(response.statusText);  // 'OK', 'Not Found', 'Internal Server Error'
console.log(response.ok);          // true jika status 200-299

// Headers
console.log(response.headers.get('content-type'));    // 'application/json'
console.log(response.headers.get('x-request-id'));

// Metadata
console.log(response.url);          // Final URL (setelah redirect)
console.log(response.redirected);   // true jika dialihkan
console.log(response.type);         // 'basic', 'cors', 'opaque'
console.log(response.bodyUsed);     // true jika body sudah dibaca

// ===== Membaca Body (hanya bisa sekali!) =====
// ⚠️ Body hanya bisa dibaca SATU KALI!
// Jika perlu membaca ulang, clone response-nya

// JSON
const jsonResponse = await fetch('/api/data');
const jsonData = await jsonResponse.json();
console.log(jsonData);

// Text
const textResponse = await fetch('/page.html');
const html = await textResponse.text();
console.log(html);

// Blob (binary — gambar, file)
const imgResponse = await fetch('/images/photo.jpg');
const imgBlob = await imgResponse.blob();
const imgUrl = URL.createObjectURL(imgBlob);
document.querySelector('img').src = imgUrl;

// ArrayBuffer (raw binary)
const audioResponse = await fetch('/audio.mp3');
const audioBuffer = await audioResponse.arrayBuffer();

// ===== Clone Response =====
const original = await fetch('/api/data');

// Clone sebelum membaca
const clone1 = original.clone();
const clone2 = original.clone();

const json = await original.json();     // ✅ OK
const text = await clone1.text();       // ✅ OK
const blob = await clone2.blob();       // ✅ OK

// ===== Custom Response =====
const customResponse = new Response(
    JSON.stringify({ message: 'Hello from Service Worker!' }),
    {
        status: 200,
        statusText: 'OK',
        headers: new Headers({
            'Content-Type': 'application/json',
            'X-Custom': 'custom-value'
        })
    }
);

6. Error Handling

JavaScript — Error Handling yang Benar
// ===== Pola Error Handling yang Robust =====
class APIError extends Error {
    constructor(message, status, response) {
        super(message);
        this.name = 'APIError';
        this.status = status;
        this.response = response;
    }
}

async function apiRequest(url, options = {}) {
    let response;

    try {
        response = await fetch(url, {
            ...options,
            headers: {
                'Content-Type': 'application/json',
                ...options.headers
            }
        });
    } catch (error) {
        // Network error — tidak ada internet, DNS failure, dll
        throw new APIError(
            'Tidak bisa terhubung ke server. Cek koneksi internet Anda.',
            0, // status 0 = network error
            null
        );
    }

    // HTTP error — 4xx, 5xx
    if (!response.ok) {
        let errorMessage;
        try {
            const errorData = await response.json();
            errorMessage = errorData.message || errorData.error;
        } catch {
            errorMessage = response.statusText;
        }

        switch (response.status) {
            case 400:
                throw new APIError(
                    errorMessage || 'Request tidak valid', 400, response
                );
            case 401:
                throw new APIError(
                    'Sesi habis. Silakan login kembali.', 401, response
                );
            case 403:
                throw new APIError(
                    'Anda tidak memiliki akses.', 403, response
                );
            case 404:
                throw new APIError(
                    'Data tidak ditemukan.', 404, response
                );
            case 429:
                throw new APIError(
                    'Terlalu banyak request. Coba lagi nanti.', 429, response
                );
            case 500:
            case 502:
            case 503:
                throw new APIError(
                    'Server sedang bermasalah. Coba lagi nanti.',
                    response.status, response
                );
            default:
                throw new APIError(
                    errorMessage || `Error: ${response.status}`,
                    response.status, response
                );
        }
    }

    // Success — parse response
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
        return await response.json();
    }
    return await response.text();
}

// Penggunaan:
try {
    const users = await apiRequest('/api/users');
    console.log(users);
} catch (error) {
    if (error instanceof APIError) {
        if (error.status === 401) {
            window.location.href = '/login';
        } else {
            showErrorNotification(error.message);
        }
    }
}

7. AbortController

AbortController memungkinkan Anda membatalkan fetch request yang sedang berjalan — sangat penting untuk mencegah race condition, membatalkan search saat user mengetik, dan timeout.

JavaScript — AbortController
// ===== Abort Fetch yang Sedang Berjalan =====

// 1. Abort Manual
const controller = new AbortController();
const { signal } = controller;

fetch('/api/data', { signal })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => {
        if (error.name === 'AbortError') {
            console.log('Fetch dibatalkan!');
        } else {
            console.error('Error:', error);
        }
    });

// Batalkan setelah 2 detik
setTimeout(() => controller.abort(), 2000);

// ===== 2. Fetch dengan Timeout =====
function fetchWithTimeout(url, options = {}, timeout = 5000) {
    const controller = new AbortController();
    const { signal } = controller;

    const timeoutId = setTimeout(() => controller.abort(), timeout);

    return fetch(url, { ...options, signal })
        .then(response => {
            clearTimeout(timeoutId);
            return response;
        });
}

// Penggunaan:
try {
    const response = await fetchWithTimeout('/api/data', {}, 3000);
    const data = await response.json();
} catch (error) {
    if (error.name === 'AbortError') {
        console.error('Request timeout setelah 3 detik!');
    }
}

// ===== 3. Debounce Search dengan Abort =====
let searchController = null;

async function searchAPI(query) {
    // Batalkan request sebelumnya jika ada
    if (searchController) {
        searchController.abort();
    }

    // Buat controller baru
    searchController = new AbortController();

    try {
        const response = await fetch(`/api/search?q=${query}`, {
            signal: searchController.signal
        });
        const results = await response.json();
        displayResults(results);
    } catch (error) {
        if (error.name === 'AbortError') {
            console.log('Search sebelumnya dibatalkan');
        } else {
            console.error('Search error:', error);
        }
    }
}

// Event listener — batalkan fetch lama saat user mengetik
document.getElementById('searchInput').addEventListener('input', (e) => {
    searchAPI(e.target.value);
});

// ===== 4. Abort Beberapa Fetch Sekaligus =====
async function fetchMultiple(urls) {
    const controller = new AbortController();
    const { signal } = controller;

    const promises = urls.map(url =>
        fetch(url, { signal }).then(r => r.json())
    );

    // Timeout untuk semua
    const timeoutId = setTimeout(() => controller.abort(), 10000);

    try {
        const results = await Promise.all(promises);
        clearTimeout(timeoutId);
        return results;
    } catch (error) {
        clearTimeout(timeoutId);
        throw error;
    }
}

// ===== 5. AbortSignal.timeout() — Shortcut =====
// API baru (tidak semua browser support)
try {
    const response = await fetch('/api/data', {
        signal: AbortSignal.timeout(5000) // Timeout 5 detik
    });
    const data = await response.json();
} catch (error) {
    if (error.name === 'TimeoutError') {
        console.error('Timeout!');
    }
}

// ===== 6. AbortSignal.any() — Gabungkan Signals =====
// Jika salah satu signal abort, fetch dibatalkan
const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(10000);

try {
    const response = await fetch('/api/data', {
        signal: AbortSignal.any([
            userController.signal,
            timeoutSignal
        ])
    });
} catch (error) {
    console.log('Fetch dibatalkan:', error.name);
}

8. Streaming Response

Fetch mendukung streaming — Anda bisa membaca response secara bertahap tanpa menunggu seluruh data selesai. Ini sangat berguna untuk file besar atau streaming API (seperti ChatGPT).

JavaScript — Streaming Response
// ===== Membaca Stream dengan ReadableStream =====
async function streamResponse(url) {
    const response = await fetch(url);

    // Dapatkan reader dari body stream
    const reader = response.body.getReader();
    const contentLength = response.headers.get('Content-Length');
    const total = parseInt(contentLength, 10);

    let receivedLength = 0;
    const chunks = [];

    while (true) {
        const { done, value } = await reader.read();

        if (done) break;

        chunks.push(value);
        receivedLength += value.length;

        // Update progress bar
        if (total) {
            const percent = Math.round((receivedLength / total) * 100);
            updateProgress(percent);
            console.log(`Diterima: ${receivedLength} dari ${total} bytes (${percent}%)`);
        }
    }

    // Gabungkan semua chunks
    const allChunks = new Uint8Array(receivedLength);
    let position = 0;
    for (const chunk of chunks) {
        allChunks.set(chunk, position);
        position += chunk.length;
    }

    return allChunks;
}

// ===== Stream Text (seperti ChatGPT/LLM API) =====
async function streamText(prompt) {
    const response = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt })
    });

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    const output = document.getElementById('output');
    output.textContent = '';

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

        const text = decoder.decode(value, { stream: true });
        output.textContent += text;

        // Auto-scroll ke bawah
        output.scrollTop = output.scrollHeight;
    }

    console.log('Streaming selesai!');
}

// ===== Download dengan Progress =====
async function downloadWithProgress(url, filename) {
    const response = await fetch(url);
    const contentLength = response.headers.get('Content-Length');
    const total = parseInt(contentLength, 10) || 0;

    const reader = response.body.getReader();
    const chunks = [];
    let received = 0;

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

        chunks.push(value);
        received += value.length;

        // Update UI
        if (total > 0) {
            const percent = ((received / total) * 100).toFixed(1);
            document.getElementById('progress').textContent =
                `${percent}% (${(received / 1024 / 1024).toFixed(1)} MB)`;
        }
    }

    // Buat blob dan download
    const blob = new Blob(chunks);
    const url2 = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url2;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url2);
}

// ===== TransformStream: Proses Data Streaming =====
async function processStream(url) {
    const response = await fetch(url);

    // Buat transform stream untuk mengubah huruf besar
    const transform = new TransformStream({
        transform(chunk, controller) {
            const text = new TextDecoder().decode(chunk);
            controller.enqueue(
                new TextEncoder().encode(text.toUpperCase())
            );
        }
    });

    const transformedStream = response.body.pipeThrough(transform);
    const reader = transformedStream.getReader();

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

    return result;
}

9. Pola HTTP Modern

HTTP Client Class

JavaScript — HTTP Client
// ===== Reusable HTTP Client =====
class HttpClient {
    constructor(baseUrl = '') {
        this.baseUrl = baseUrl;
        this.defaultHeaders = {};
        this.interceptors = { request: [], response: [] };
    }

    setHeader(key, value) {
        this.defaultHeaders[key] = value;
    }

    addRequestInterceptor(fn) {
        this.interceptors.request.push(fn);
    }

    addResponseInterceptor(fn) {
        this.interceptors.response.push(fn);
    }

    async request(endpoint, options = {}) {
        let url = `${this.baseUrl}${endpoint}`;

        let config = {
            ...options,
            headers: {
                ...this.defaultHeaders,
                ...options.headers
            }
        };

        // Jalankan request interceptors
        for (const interceptor of this.interceptors.request) {
            config = await interceptor(config);
        }

        let response = await fetch(url, config);

        // Jalankan response interceptors
        for (const interceptor of this.interceptors.response) {
            response = await interceptor(response);
        }

        if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw { status: response.status, ...error };
        }

        return response.json();
    }

    get(endpoint, options = {}) {
        return this.request(endpoint, { ...options, method: 'GET' });
    }

    post(endpoint, data, options = {}) {
        return this.request(endpoint, {
            ...options,
            method: 'POST',
            body: JSON.stringify(data)
        });
    }

    put(endpoint, data, options = {}) {
        return this.request(endpoint, {
            ...options,
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }

    delete(endpoint, options = {}) {
        return this.request(endpoint, { ...options, method: 'DELETE' });
    }
}

// Penggunaan:
const api = new HttpClient('https://jsonplaceholder.typicode.com');
api.setHeader('Content-Type', 'application/json');

// Tambah token otomatis
api.addRequestInterceptor(async (config) => {
    const token = localStorage.getItem('token');
    if (token) {
        config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
});

const posts = await api.get('/posts');
const newPost = await api.post('/posts', { title: 'Test', body: 'Isi' });

Retry Pattern

JavaScript — Retry & Exponential Backoff
// ===== Retry dengan Exponential Backoff =====
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
    let lastError;

    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch(url, options);

            if (response.ok) return response;

            // Jangan retry untuk error client (4xx)
            if (response.status >= 400 && response.status < 500) {
                throw new Error(`Client Error: ${response.status}`);
            }

            // Server error (5xx) → bisa di-retry
            lastError = new Error(`Server Error: ${response.status}`);

        } catch (error) {
            lastError = error;

            // Jangan retry untuk error yang bukan network/server
            if (error.message.startsWith('Client Error')) {
                throw error;
            }
        }

        // Tunggu sebelum retry (exponential backoff)
        if (attempt < maxRetries) {
            const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
            const jitter = delay * 0.5 * Math.random();
            const waitTime = delay + jitter;

            console.log(
                `Retry ${attempt + 1}/${maxRetries} dalam ${waitTime.toFixed(0)}ms...`
            );
            await new Promise(resolve => setTimeout(resolve, waitTime));
        }
    }

    throw lastError;
}

// Penggunaan:
try {
    const response = await fetchWithRetry('/api/data', {}, 3);
    const data = await response.json();
} catch (error) {
    console.error('Gagal setelah 3 kali retry:', error);
}

10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut:

Pertanyaan 1: Apa yang dikembalikan oleh fetch()?

a) Response object langsung
b) Promise yang resolve ke Response object
c) Promise yang resolve ke JSON
d) Observable

Pertanyaan 2: Kapan fetch() reject promise-nya?

a) Saat server mengembalikan 404
b) Saat server mengembalikan 500
c) Hanya saat network error (tidak ada internet)
d) Saat response bukan JSON

Pertanyaan 3: Bagaimana cara membatalkan fetch request yang sedang berjalan?

a) fetch.cancel()
b) response.close()
c) AbortController.abort()
d) fetch.stop()

Pertanyaan 4: Mengapa response perlu di-clone sebelum dibaca?

a) Agar lebih cepat
b) Karena body response hanya bisa dibaca SATU KALI
c) Untuk mengenkripsi data
d) Tidak perlu di-clone

Pertanyaan 5: Untuk mengupload file, format apa yang tepat untuk body request?

a) JSON
b) FormData
c) URLSearchParams
d) Text
🔍 Zoom
100%
🎨 Tema