Academic Block

Fetch API Get Certified

The Fetch API is the modern way to make HTTP requests from the browser. It returns Promises, works cleanly with async/await, and replaces the older XMLHttpRequest for most everyday use cases.

Because this tutorial runs in a sandboxed editor, we use a mockFetch helper that behaves like the real fetch() so every example produces visible output.

Basic Fetch Request

Calling fetch(url) returns a Promise that resolves to a Response object. You then call a body method such as .json() to read the data.

What this code does: Performs a basic mock GET request and logs the parsed JSON response.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: true,
                status: 200,
                json: function () {
                    return Promise.resolve({ id: 1, name: "Alice" });
                }
            });
        }, 80);
    });
}

console.log("Fetching user...");

mockFetch("/api/user")
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log("User:", data);
    });

console.log("Request started");

Checking response.ok

fetch() only rejects on network failure. HTTP error statuses (404, 500, etc.) still resolve. Always check response.ok before treating the response as success.

What this code does: Shows how to detect an HTTP error by testing response.ok and throwing when the status is not OK.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: false,
                status: 404,
                json: function () {
                    return Promise.resolve({ error: "Not found" });
                }
            });
        }, 80);
    });
}

console.log("Fetching missing resource...");

mockFetch("/api/missing")
    .then(function (response) {
        if (!response.ok) {
            throw new Error("HTTP error: " + response.status);
        }
        return response.json();
    })
    .then(function (data) {
        console.log(data);
    })
    .catch(function (error) {
        console.log("Caught:", error.message);
    });

console.log("Request started");

Using async/await with Fetch

Because fetch() returns a promise, it works naturally with async/await, which often reads more clearly than a long .then() chain.

What this code does: Rewrites the basic fetch flow using async/await for a cleaner sequential style.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: true,
                status: 200,
                json: function () {
                    return Promise.resolve({ id: 1, name: "Michael" });
                }
            });
        }, 80);
    });
}

async function getUser() {
    console.log("Fetching with async/await...");
    const response = await mockFetch("/api/user");
    const data = await response.json();
    console.log(data);
}

getUser();
console.log("getUser() called");

Error Handling with try…catch

With async/await, both network errors and manually thrown HTTP errors are caught by the same try...catch block.

What this code does: Combines async/await with try...catch so both network failures and HTTP errors are handled in one place.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: false,
                status: 500,
                json: function () {
                    return Promise.resolve({ error: "Server error" });
                }
            });
        }, 80);
    });
}

async function getUser() {
    try {
        console.log("Fetching...");
        const response = await mockFetch("/api/user");
        if (!response.ok) {
            throw new Error("HTTP error: " + response.status);
        }
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.log("Error:", error.message);
    }
}

getUser();
console.log("getUser() called");

Sending Data with a POST Request

To send data, pass a second options object to fetch() with a method, headers, and a body. The body is usually a JSON string created with JSON.stringify().

What this code does: Shows how to send a POST request by passing a second options object that contains the method, headers, and JSON body.

function mockFetch(url, options) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log("Request method:", options.method);
            console.log("Request body:", options.body);
            resolve({
                ok: true,
                status: 201,
                json: function () {
                    return Promise.resolve({ id: 2, created: true });
                }
            });
        }, 80);
    });
}

const newUser = {
    name: "David",
    age: 32
};

console.log("Sending POST...");

mockFetch("/api/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify(newUser)
})
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log("Created:", data);
    });

console.log("POST started");

Fetching Multiple Resources in Parallel

When several requests do not depend on each other, start them all first, then use Promise.all() to wait for every one of them together.

What this code does: Starts several independent requests at the same time and waits for all of them with Promise.all().

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: true,
                status: 200,
                json: function () {
                    return Promise.resolve({ url: url });
                }
            });
        }, 80);
    });
}

async function getAllData() {
    console.log("Fetching in parallel...");
    const results = await Promise.all([
        mockFetch("/api/users").then(function (r) { return r.json(); }),
        mockFetch("/api/posts").then(function (r) { return r.json(); })
    ]);
    console.log("Users:", results[0]);
    console.log("Posts:", results[1]);
}

getAllData();
console.log("getAllData() called");

Canceling a Request with AbortController

An AbortController lets you cancel a fetch request that is already in progress. The real fetch() accepts a signal option; this mock recreates the same behavior.

What this code does: Creates an AbortController, passes its signal to the request, and aborts the request after a short timeout so it never completes.

function mockFetch(url, options) {
    return new Promise(function (resolve, reject) {
        const timer = setTimeout(function () {
            resolve({
                ok: true,
                status: 200,
                json: function () {
                    return Promise.resolve({ url: url });
                }
            });
        }, 300);

        if (options && options.signal) {
            options.signal.addEventListener("abort", function () {
                clearTimeout(timer);
                reject(new Error("Fetch aborted"));
            });
        }
    });
}

const controller = new AbortController();

console.log("Starting abortable request...");

mockFetch("/api/slow", { signal: controller.signal })
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log(data);
    })
    .catch(function (error) {
        console.log("Caught:", error.message);
    });

setTimeout(function () {
    controller.abort();
    console.log("Abort signal sent");
}, 50);

Fetch API vs. XMLHttpRequest

Feature Fetch API XMLHttpRequest
Based on promises? Yes No (event callbacks)
Works with async/await? Yes Not directly
Rejects on HTTP errors (404, 500)? No — only on network failure No — status must be checked
Built-in request cancellation AbortController xhr.abort()
Syntax Modern, concise Older, more verbose

Common Mistakes with Fetch

  • Forgetting that fetch() does not reject on HTTP error statuses like 404 or 500.
  • Forgetting to call .json() (or another body method) to actually read the response data.
  • Forgetting a .catch() or try...catch, leaving network errors unhandled.
  • Forgetting to set "Content-Type": "application/json" when sending a JSON body.
  • Forgetting to call JSON.stringify() on the body of a request.

What this code does: Illustrates a typical mistake: not checking response.ok before reading the body.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: false,
                status: 404,
                json: function () {
                    return Promise.resolve({ error: "Not found" });
                }
            });
        }, 80);
    });
}

console.log("Mistake demo (no ok check)...");

mockFetch("/api/missing")
    .then(function (response) {
        return response.json();
    })
    .then(function (data) {
        console.log("This runs even though it 404'd:", data);
    });

console.log("Request started");

Best Practices for Fetch

  • Always check response.ok before treating the response as successful.
  • Always handle both network errors (.catch() / try...catch) and HTTP errors (response.ok).
  • Use Promise.all() to fetch independent resources in parallel instead of one after another.
  • Use AbortController to cancel requests that are no longer needed.
  • Set the correct Content-Type header when sending a request body.

Quick Summary

  • fetch(url) returns a promise that resolves to a Response object.
  • Reading the body with .json() or .text() returns another promise.
  • fetch() only rejects on network failure; HTTP errors must be checked with response.ok.
  • fetch() works naturally with async/await.
  • Pass a second options object to send a method, headers, and body.
  • Use Promise.all() for parallel requests and AbortController to cancel a request.

Try It Yourself

Modify the function below. Try changing ok to false, adjusting the delay, or adding a second mockFetch() call combined with Promise.all().

What this code does: A complete practice function that fetches data, checks response.ok, parses JSON, and uses try...catch...finally for robust error handling.

function mockFetch(url) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve({
                ok: true,
                status: 200,
                json: function () {
                    return Promise.resolve({ name: "Michael", role: "Developer" });
                }
            });
        }, 80);
    });
}

async function main() {
    try {
        console.log("Fetching...");
        const response = await mockFetch("/api/user");
        if (!response.ok) {
            throw new Error("HTTP error: " + response.status);
        }
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.log("Error:", error.message);
    } finally {
        console.log("Request finished");
    }
}

main();
console.log("main() called");
Tip: Remember that fetch() only rejects on a network failure — always check response.ok for HTTP errors, pair it comfortably with async/await, and reach for Promise.all() or AbortController when requests need to run together or be canceled.

Understanding the Fetch API builds directly on promises and async/await, and is the foundation for working with real APIs, loading data into a page, and building modern web applications.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close