Fetch
The browser's fetch function sends HTTP requests from JavaScript and returns a promise of the response. Chain .then to read the JSON, check response.ok before trusting it, and pass an options object to POST, PUT or DELETE.
Apprentice · JavaScript · REST API · Updated
After this lesson you can
- Send a GET request with fetch, and read the JSON from the response with then
- Send POST, PUT and DELETE requests with fetch, with a JSON body and a Content-Type header, and check response.ok
Key takeaways
fetch(url)sends aGETand returns a promise;.thensays what to do when it's kept, andresponse.json()reads the JSON body.fetchkeeps its promise even for a404or a500, so checkresponse.okbefore trusting the response.- To send data, pass an options object with the
method, aContent-Type: application/jsonheader andbody: JSON.stringify(item). .catchruns when athenthrows, and when there's no answer at all, such as when the server is down.
In Calling APIs from C#, HttpClient called your categories API from a C# program. A web page can call it too, from JavaScript, with a function built into every browser: fetch.
Putting the page beside the API
A page can call an API freely when both come from the same origin: the same address and port. So the simplest setup is to let the API serve the page. Add a wwwroot folder to the NorthwindApi project from HTTP and Web APIs, and two lines to its Program.cs, before app.UseHttpsRedirection();:
app.UseDefaultFiles();
app.UseStaticFiles();
UseStaticFiles serves the files in wwwroot, and UseDefaultFiles sends wwwroot/index.html when someone asks for /. Now http://localhost:5179/ is your page, and /api/categories is your API, on the same origin.
(A page from somewhere else, such as an Angular app on its own port, is blocked by the browser unless the API says that origin is allowed. That's called CORS. When you build an Angular app, you'll see a simpler way around it.)
Open the page, press F12, and you can run every example in this lesson in the Console.
A GET with fetch
fetch('/api/categories/8')
.then(response => response.json())
.then(category => console.log(`${category.categoryName}: ${category.description}`));
Seafood: Seaweed and fish
fetch('/api/categories/8') sends a GET to the page's own origin. But a request takes time, and JavaScript doesn't stop and wait. Instead, fetch returns a promise: an object that promises a result later, like a C# Task.
.then(callback) says what to do when the promise is kept. Here there are two steps:
- The first
thengets the response: status, headers and a body still arriving.response.json()reads the body and parses the JSON, and it returns another promise. - The second
thengets the parsed object,category, with the same camelCase names as the JSON.
Each then hands its result to the next, so the steps line up in order, like a LINQ chain.
Checking the response
fetch keeps its promise whenever the server answers, even with a 404 or a 500. So check response.ok, which is true for 2xx codes, like IsSuccessStatusCode:
fetch('/api/categories/300')
.then(response => {
console.log(response.status, response.ok);
if (!response.ok) {
throw new Error(`The API answered ${response.status}`);
}
return response.json();
})
.then(category => console.log(category.categoryName))
.catch(error => console.log(error.message));
404 false
The API answered 404
- Throwing inside a
thenskips the rest of the chain and jumps to.catch(callback), like acatchblock. .catchalso runs when there's no answer at all: the server is down, or the network failed. Thenfetchitself breaks its promise, likeHttpRequestException.
Sending data: POST, PUT and DELETE
To send anything but a GET, give fetch a second argument: an options object with the method, the headers and the body. The body must be text, so JSON.stringify turns your object into JSON:
const snacks = { categoryName: 'Snacks', description: 'Chips, nuts and pretzels' };
fetch('/api/categories', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(snacks)
})
.then(response => {
console.log(response.status);
return response.json();
})
.then(added => console.log(`Added with id ${added.categoryId}`));
201
Added with id 9
Without the Content-Type header, the API wouldn't know the body is JSON, and would answer 415 Unsupported Media Type.
PUT works the same way, and answers with the category as it's now saved, so there's JSON to read, as after a POST. A DELETE sends nothing back: there's just the status to check.
const changed = { categoryId: 9, categoryName: 'Snacks', description: 'Chips, nuts, pretzels and popcorn' };
fetch('/api/categories/9', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(changed)
})
.then(response => {
console.log(`PUT: ${response.status}`);
return response.json();
})
.then(saved => console.log(`Now: ${saved.description}`))
.then(() => fetch('/api/categories/9', { method: 'DELETE' }))
.then(response => console.log(`DELETE: ${response.status}`));
PUT: 200
Now: Chips, nuts, pretzels and popcorn
DELETE: 204
The first then returns response.json()'s promise, so the second gets the saved category. The third then returns a new fetch's promise, so the DELETE waits until the PUT is done, and the last then gets the DELETE's response.
| To | fetch options |
|---|---|
| Read | none: fetch(url) is a GET |
| Create | method: 'POST', a Content-Type header, body: JSON.stringify(item) |
| Replace | method: 'PUT', a Content-Type header, body: JSON.stringify(item) |
| Delete | method: 'DELETE' |
Check yourself
2 questions to try on the lesson page.
So what
fetch is HttpClient for the browser: a URL, a method, maybe a JSON body, and a promise of the response. Chains of then read nicely for one or two steps; the next lesson shows a way to write longer ones that reads like ordinary code.