JavaScript in the browser
Load a reading suggestion with Fetch
Build and test a browser suggestion loader with loading, empty, HTTP error, parsing error, and offline states.
We’ll add a suggested article to the reading-list page from the final JavaScript project. This lesson builds just the loading button. Later you can connect the result to the list you save in local storage.
The Fetch API guide covers request options, forms, credentials, caching, streams, and cancellation. Here we need a GET request and a reliable interface when it fails.
Create a local response
In a folder served by your local development server, create suggestions.json:
[
{
"title": "JavaScript promises",
"url": "https://flaviocopes.com/javascript-promises/"
}
]
Serve the page over HTTP. Opening it as a file: URL can prevent Fetch from reading the JSON. Use your existing dev server, or run npx serve . in this practice folder and open the local URL it prints.
Build the loading button
Create index.html. We keep the message separate from the result so an empty response has somewhere to explain itself:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reading suggestion</title>
</head>
<body>
<button id="load" type="button">Load a suggestion</button>
<p id="status" role="status"></p>
<div id="result"></div>
<script type="module" src="app.js"></script>
</body>
</html>
Create app.js. The first await gets the response; the second reads and parses its body:
const button = document.querySelector('#load')
const status = document.querySelector('#status')
const result = document.querySelector('#result')
button.addEventListener('click', async () => {
button.disabled = true
status.textContent = 'Loading a suggestion…'
result.replaceChildren()
try {
const response = await fetch('./suggestions.json')
if (!response.ok) {
throw new Error(`Could not load suggestions (HTTP ${response.status})`)
}
const suggestions = await response.json()
if (!Array.isArray(suggestions)) {
throw new Error('Expected a list of suggestions')
}
if (suggestions.length === 0) {
status.textContent = 'No suggestions available'
return
}
const suggestion = suggestions[0]
if (!suggestion || typeof suggestion.title !== 'string' ||
typeof suggestion.url !== 'string') {
throw new Error('Invalid suggestion')
}
const url = new URL(suggestion.url)
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Unsupported link protocol')
}
const link = document.createElement('a')
link.href = url.href
link.textContent = suggestion.title
result.append(link)
status.textContent = 'Suggestion loaded'
} catch (error) {
status.textContent = error.message
} finally {
button.disabled = false
}
})
We use textContent for the title so returned text stays text. URL validation also matters: a link can run code if you allow a javascript: URL.
The finally block enables the button after every outcome, including the early return for an empty array. Clearing the old result prevents a failed request from leaving a stale suggestion on screen.
Verify each outcome
Open DevTools before clicking the button. Inspect the request’s status and raw response in Network, then compare them with the page:
| Change | Expected result |
|---|---|
| Original JSON | A link and Suggestion loaded |
Replace the file contents with [] | No suggestions available, no old link |
| Rename the JSON file | An HTTP error message, button enabled |
Put broken JSON in the file | A parsing error, button enabled |
Replace the array with {} | Expected a list of suggestions |
Use a javascript: URL | Unsupported link protocol, no link |
| Use Network’s Offline setting | A request error, button enabled |
Restore the file and network setting after each check. Under network throttling, click repeatedly: only one request should start while the button is disabled.
A 404 still resolves the Fetch promise, which is why we check response.ok. Network failures reject it. Invalid JSON rejects the body-reading promise. All three reach this catch block, but at different steps.
Extend the exercise
Add a Cancel button using AbortController and pass its signal to Fetch. Show Loading cancelled for AbortError, and verify that cancellation also enables the load button. Use the cancellation section of the guide for the API details.
Then connect a successful suggestion to the reading list. Update your state array before rendering, following the same path as a manually added item.
Lesson completed