This commit is contained in:
Ilya Kantor 2019-08-09 23:03:18 +03:00
parent e82885055b
commit 207fbe848f
3 changed files with 51 additions and 40 deletions

View file

@ -1,10 +1,9 @@
To fetch a user we need: To fetch a user we need: `fetch('https://api.github.com/users/USERNAME')`.
1. `fetch('https://api.github.com/users/USERNAME')`. If the response has status `200`, call `.json()` to read the JS object.
2. If the response has status `200`, call `.json()` to read the JS object.
If a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray. Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray.
So here's the code: So here's the code:
@ -38,4 +37,4 @@ Please note: `.then` call is attached directly to `fetch`, so that when we have
If we used `await Promise.all(names.map(name => fetch(...)))`, and call `.json()` on the results, then it would wait for all fetches to respond. By adding `.json()` directly to each `fetch`, we ensure that individual fetches start reading data as JSON without waiting for each other. If we used `await Promise.all(names.map(name => fetch(...)))`, and call `.json()` on the results, then it would wait for all fetches to respond. By adding `.json()` directly to each `fetch`, we ensure that individual fetches start reading data as JSON without waiting for each other.
That's an example of how low-level `Promise` API can still be useful even if we mainly use `async/await`. That's an example of how low-level Promise API can still be useful even if we mainly use `async/await`.

View file

@ -8,5 +8,6 @@ There's a test example in the sandbox.
Important details: Important details:
1. There should be one `fetch` request per user. And requests shouldn't wait for each other. So that the data arrives as soon as possible. 1. There should be one `fetch` request per user.
2. If any request fails, or if there's no such user, the function should return `null` in the resulting array. 2. Requests shouldn't wait for each other. So that the data arrives as soon as possible.
3. If any request fails, or if there's no such user, the function should return `null` in the resulting array.

View file

@ -3,7 +3,7 @@
JavaScript can send network requests to the server and load new information whenever is needed. JavaScript can send network requests to the server and load new information whenever is needed.
For example, we can: For example, we can use a network request to:
- Submit an order, - Submit an order,
- Load user information, - Load user information,
@ -12,11 +12,11 @@ For example, we can:
...And all of that without reloading the page! ...And all of that without reloading the page!
There's an umbrella term "AJAX" (abbreviated <b>A</b>synchronous <b>J</b>avascript <b>A</b>nd <b>X</b>ml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. There's an umbrella term "AJAX" (abbreviated <b>A</b>synchronous <b>J</b>avaScript <b>A</b>nd <b>X</b>ML) for network requests from JavaScript. We don't have to use XML though: the term comes from old times, that's that word is there. You may have heard that term already.
There are multiple ways to send a network request and get information from the server. There are multiple ways to send a network request and get information from the server.
The `fetch()` method is modern and versatile, so we'll start with it. It evolved for several years and continues to improve, right now the support is pretty solid among browsers. The `fetch()` method is modern and versatile, so we'll start with it. It's not supported by old browsers (can be polyfilled), but very well supported among the new ones.
The basic syntax is: The basic syntax is:
@ -27,20 +27,20 @@ let promise = fetch(url, [options])
- **`url`** -- the URL to access. - **`url`** -- the URL to access.
- **`options`** -- optional parameters: method, headers etc. - **`options`** -- optional parameters: method, headers etc.
The browser starts the request right away and returns a `promise`. The browser starts the request right away and returns a promise that the calling code should use to get the result.
Getting a response is usually a two-stage process. Getting a response is usually a two-stage process.
**First, the `promise` resolves with an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class as soon as the server responds with headers.** **First, the `promise` resolves with an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class as soon as the server responds with headers.**
So we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet. At this stage we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.
The promise rejects if the `fetch` was unable to make HTTP-request, e.g. network problems, or there's no such site. HTTP-errors, even such as 404 or 500, are considered a normal flow. The promise rejects if the `fetch` was unable to make HTTP-request, e.g. network problems, or there's no such site. Abnormal HTTP-statuses, such as 404 or 500 do not cause an error.
We can see them in response properties: We can see HTTP-status in response properties:
- **`status`** -- HTTP status code, e.g. 200.
- **`ok`** -- boolean, `true` if the HTTP status code is 200-299. - **`ok`** -- boolean, `true` if the HTTP status code is 200-299.
- **`status`** -- HTTP status code.
For example: For example:
@ -48,7 +48,7 @@ For example:
let response = await fetch(url); let response = await fetch(url);
if (response.ok) { // if HTTP-status is 200-299 if (response.ok) { // if HTTP-status is 200-299
// get the response body (see below) // get the response body (the method explained below)
let json = await response.json(); let json = await response.json();
} else { } else {
alert("HTTP-Error: " + response.status); alert("HTTP-Error: " + response.status);
@ -59,17 +59,18 @@ if (response.ok) { // if HTTP-status is 200-299
`Response` provides multiple promise-based methods to access the body in various formats: `Response` provides multiple promise-based methods to access the body in various formats:
- **`response.json()`** -- parse the response as JSON object, - **`response.text()`** -- read the response and return as text,
- **`response.text()`** -- return the response as text, - **`response.json()`** -- parse the response as JSON,
- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), - **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)),
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), - **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data),
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later. - additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later.
For instance, let's get a JSON-object with latest commits from GitHub: For instance, let's get a JSON-object with latest commits from GitHub:
```js run async ```js run async
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
*!* *!*
let commits = await response.json(); // read response body and parse as JSON let commits = await response.json(); // read response body and parse as JSON
@ -86,7 +87,8 @@ fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi
.then(commits => alert(commits[0].author.login)); .then(commits => alert(commits[0].author.login));
``` ```
To get the text, `await response.text()` instead of `.json()`: To get the reponse text, `await response.text()` instead of `.json()`:
```js run async ```js run async
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
@ -95,7 +97,7 @@ let text = await response.text(); // read response body as text
alert(text.slice(0, 80) + '...'); alert(text.slice(0, 80) + '...');
``` ```
As a show-case for reading in binary format, let's fetch and show an image (see chapter [Blob](info:blob) for details about operations on blobs): As a show-case for reading in binary format, let's fetch and show a logo image of ["fetch" specification](https://fetch.spec.whatwg.org) (see chapter [Blob](info:blob) for details about operations on `Blob`):
```js async run ```js async run
let response = await fetch('/article/fetch/logo-fetch.svg'); let response = await fetch('/article/fetch/logo-fetch.svg');
@ -119,20 +121,20 @@ setTimeout(() => { // hide after three seconds
``` ```
````warn ````warn
We can choose only one body-parsing method. We can choose only one body-reading method.
If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed. If we've already got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed.
```js ```js
let text = await response.text(); // response body consumed let text = await response.text(); // response body consumed
let parsed = await response.json(); // fails (already consumed) let parsed = await response.json(); // fails (already consumed)
```` ````
## Headers ## Response headers
There's a Map-like headers object in `response.headers`. The response headers are available in a Map-like headers object in `response.headers`.
We can get individual headers or iterate over them: It's not exactly a Map, but it has similar methods to get individual headers by name or iterate over them:
```js run async ```js run async
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
@ -146,12 +148,14 @@ for (let [key, value] of response.headers) {
} }
``` ```
To set a header, we can use the `headers` option, like this: ## Request headers
To set a request header in `fetch`, we can use the `headers` option. It has an object with outgoing headers, like this:
```js ```js
let response = fetch(protectedUrl, { let response = fetch(protectedUrl, {
headers: { headers: {
Authentication: 'abcdef' Authentication: 'secret'
} }
}); });
``` ```
@ -186,12 +190,14 @@ These headers ensure proper and safe HTTP, so they are controlled exclusively by
To make a `POST` request, or a request with another method, we need to use `fetch` options: To make a `POST` request, or a request with another method, we need to use `fetch` options:
- **`method`** -- HTTP-method, e.g. `POST`, - **`method`** -- HTTP-method, e.g. `POST`,
- **`body`** -- one of: - **`body`** -- the request body, one of:
- a string (e.g. JSON), - a string (e.g. JSON-encoded),
- `FormData` object, to submit the data as `form/multipart`, - `FormData` object, to submit the data as `form/multipart`,
- `Blob`/`BufferSource` to send binary data, - `Blob`/`BufferSource` to send binary data,
- [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used.
The JSON format is used most of the time.
For example, this code submits `user` object as JSON: For example, this code submits `user` object as JSON:
```js run async ```js run async
@ -214,13 +220,15 @@ let result = await response.json();
alert(result.message); alert(result.message);
``` ```
Please note, if the body is a string, then `Content-Type` is set to `text/plain;charset=UTF-8` by default. So we use `headers` option to send `application/json` instead, that's the correct content type for JSON-encoded data. Please note, if the request `body` is a string, then `Content-Type` header is set to `text/plain;charset=UTF-8` by default.
But, as we're going to send JSON, we use `headers` option to send `application/json` instead, the correct `Content-Type` for JSON-encoded data.
## Sending an image ## Sending an image
We can also submit binary data directly using `Blob` or `BufferSource`. We can also submit binary data with `fetch` using `Blob` or `BufferSource` objects.
For example, here's a `<canvas>` where we can draw by moving a mouse. A click on the "submit" button sends the image to server: In this example, there's a `<canvas>` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to server:
```html run autorun height="90" ```html run autorun height="90"
<body style="margin:0"> <body style="margin:0">
@ -241,6 +249,8 @@ For example, here's a `<canvas>` where we can draw by moving a mouse. A click on
method: 'POST', method: 'POST',
body: blob body: blob
}); });
// the server responds with confirmation and the image size
let result = await response.json(); let result = await response.json();
alert(result.message); alert(result.message);
} }
@ -249,7 +259,7 @@ For example, here's a `<canvas>` where we can draw by moving a mouse. A click on
</body> </body>
``` ```
Here we also didn't need to set `Content-Type` manually, because a `Blob` object has a built-in type (here `image/png`, as generated by `toBlob`). Please note, here we don't set `Content-Type` header manually, because a `Blob` object has a built-in type (here `image/png`, as generated by `toBlob`). For `Blob` objects that type becomes the value of `Content-Type`.
The `submit()` function can be rewritten without `async/await` like this: The `submit()` function can be rewritten without `async/await` like this:
@ -275,7 +285,8 @@ let response = await fetch(url, options); // resolves with response headers
let result = await response.json(); // read body as json let result = await response.json(); // read body as json
``` ```
Or, promise-style: Or, without `await`:
```js ```js
fetch(url, options) fetch(url, options)
.then(response => response.json()) .then(response => response.json())
@ -288,15 +299,15 @@ Response properties:
- `response.headers` -- Map-like object with HTTP headers. - `response.headers` -- Map-like object with HTTP headers.
Methods to get response body: Methods to get response body:
- **`response.json()`** -- parse the response as JSON object,
- **`response.text()`** -- return the response as text, - **`response.text()`** -- return the response as text,
- **`response.json()`** -- parse the response as JSON object,
- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter), - **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter),
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), - **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data),
Fetch options so far: Fetch options so far:
- `method` -- HTTP-method, - `method` -- HTTP-method,
- `headers` -- an object with request headers (not any header is allowed), - `headers` -- an object with request headers (not any header is allowed),
- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. - `body` -- the data to send (request body) as `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object.
In the next chapters we'll see more options and use cases of `fetch`. In the next chapters we'll see more options and use cases of `fetch`.