Skip to content

Commit 52c20b4

Browse files
committed
add 5-network
1 parent 74c4775 commit 52c20b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+23812
-0
lines changed

5-network/01-fetch-basics/article.md

+361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
2+
# Fetch: Basics
3+
4+
Method `fetch()` is the modern way of sending requests over HTTP.
5+
6+
It evolved for several years and continues to improve, right now the support is pretty solid among browsers.
7+
8+
The basic syntax is:
9+
10+
```js
11+
let promise = fetch(url, [options])
12+
```
13+
14+
- **`url`** -- the URL to access.
15+
- **`options`** -- optional parameters: method, headers etc.
16+
17+
The browser starts the request right away and returns a `promise`.
18+
19+
Getting a response is usually a two-stage process.
20+
21+
**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.**
22+
23+
24+
So we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.
25+
26+
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.
27+
28+
We can see them in response properties:
29+
30+
- **`ok`** -- boolean, `true` if the HTTP status code is 200-299.
31+
- **`status`** -- HTTP status code.
32+
33+
For example:
34+
35+
```js
36+
let response = await fetch(url);
37+
38+
if (response.ok) { // if HTTP-status is 200-299
39+
// get the response body (see below)
40+
let json = await response.json();
41+
} else {
42+
alert("HTTP-Error: " + response.status);
43+
}
44+
```
45+
46+
To get the response body, we need to use an additional method call.
47+
48+
`Response` provides multiple promise-based methods to access the body in various formats:
49+
50+
- **`response.json()`** -- parse the response as JSON object,
51+
- **`response.text()`** -- return the response as text,
52+
- **`response.formData()`** -- return the response as FormData object (form/multipart encoding),
53+
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
54+
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data),
55+
- 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.
56+
57+
For instance, here we get a JSON-object with latest commits from GitHub:
58+
59+
```js run async
60+
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
61+
62+
*!*
63+
let commits = await response.json(); // read response body and parse as JSON
64+
*/!*
65+
66+
alert(commits[0].author.login);
67+
```
68+
69+
Or, the same using pure promises syntax:
70+
71+
```js run
72+
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
73+
.then(response => response.json())
74+
.then(commits => alert(commits[0].author.login));
75+
```
76+
77+
To get the text:
78+
```js
79+
let text = await response.text();
80+
```
81+
82+
And for the binary example, let's fetch and show an image (see chapter [Blob](info:blob) for details about operations on blobs):
83+
84+
```js async run
85+
let response = await fetch('/article/fetch/logo-fetch.svg');
86+
87+
*!*
88+
let blob = await response.blob(); // download as Blob object
89+
*/!*
90+
91+
// create <img> for it
92+
let img = document.createElement('img');
93+
img.style = 'position:fixed;top:10px;left:10px;width:100px';
94+
document.body.append(img);
95+
96+
// show it
97+
img.src = URL.createObjectURL(blob);
98+
99+
setTimeout(() => { // hide after two seconds
100+
img.remove();
101+
URL.revokeObjectURL(img.src);
102+
}, 2000);
103+
```
104+
105+
````warn
106+
We can choose only one body-parsing method.
107+
108+
If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed.
109+
110+
```js
111+
let text = await response.text(); // response body consumed
112+
let parsed = await response.json(); // fails (already consumed)
113+
````
114+
115+
## Headers
116+
117+
There's a Map-like headers object in `response.headers`.
118+
119+
We can get individual headers or iterate over them:
120+
121+
```js run async
122+
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
123+
124+
// get one header
125+
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
126+
127+
// iterate over all headers
128+
for (let [key, value] of response.headers) {
129+
alert(`${key} = ${value}`);
130+
}
131+
```
132+
133+
To set a header, we can use the `headers` option, like this:
134+
135+
```js
136+
let response = fetch(protectedUrl, {
137+
headers: {
138+
Authentication: 'abcdef'
139+
}
140+
});
141+
```
142+
143+
...But there's a list of [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name) that we can't set:
144+
145+
- `Accept-Charset`, `Accept-Encoding`
146+
- `Access-Control-Request-Headers`
147+
- `Access-Control-Request-Method`
148+
- `Connection`
149+
- `Content-Length`
150+
- `Cookie`, `Cookie2`
151+
- `Date`
152+
- `DNT`
153+
- `Expect`
154+
- `Host`
155+
- `Keep-Alive`
156+
- `Origin`
157+
- `Referer`
158+
- `TE`
159+
- `Trailer`
160+
- `Transfer-Encoding`
161+
- `Upgrade`
162+
- `Via`
163+
- `Proxy-*`
164+
- `Sec-*`
165+
166+
These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser.
167+
168+
## POST requests
169+
170+
To make a `POST` request, or a request with another method, we need to use `fetch` options:
171+
172+
- **`method`** -- HTTP-method, e.g. `POST`,
173+
- **`body`** -- one of:
174+
- a string (e.g. JSON),
175+
- `FormData` object, to submit the data as `form/multipart`,
176+
- `Blob`/`BufferSource` to send binary data,
177+
- [URLSearchParams](info:url), to submit the data as `x-www-form-urlencoded`, rarely used.
178+
179+
Let's see examples.
180+
181+
## Submit JSON
182+
183+
This code submits a `user` object as JSON:
184+
185+
```js run async
186+
let user = {
187+
name: 'John',
188+
surname: 'Smith'
189+
};
190+
191+
*!*
192+
let response = await fetch('/article/fetch-basics/post/user', {
193+
method: 'POST',
194+
headers: {
195+
'Content-Type': 'application/json;charset=utf-8'
196+
},
197+
body: JSON.stringify(user)
198+
});
199+
*/!*
200+
201+
let result = await response.json();
202+
alert(result.message);
203+
```
204+
205+
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.
206+
207+
## Submit a form
208+
209+
Let's do the same with an HTML `<form>`.
210+
211+
212+
```html run
213+
<form id="formElem">
214+
<input type="text" name="name" value="John">
215+
<input type="text" name="surname" value="Smith">
216+
</form>
217+
218+
<script>
219+
(async () => {
220+
let response = await fetch('/article/fetch-basics/post/user', {
221+
method: 'POST',
222+
*!*
223+
body: new FormData(formElem)
224+
*/!*
225+
});
226+
227+
let result = await response.json();
228+
229+
alert(result.message);
230+
})();
231+
</script>
232+
```
233+
234+
Here [FormData](https://xhr.spec.whatwg.org/#formdata) automatically encodes the form, `<input type="file">` fields are handled also, and sends it using `Content-Type: form/multipart`.
235+
236+
## Submit an image
237+
238+
We can also submit binary data directly using `Blob` or `BufferSource`.
239+
240+
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:
241+
242+
```html run autorun height="90"
243+
<body style="margin:0">
244+
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
245+
246+
<input type="button" value="Submit" onclick="submit()">
247+
248+
<script>
249+
canvasElem.onmousemove = function(e) {
250+
let ctx = canvasElem.getContext('2d');
251+
ctx.lineTo(e.clientX, e.clientY);
252+
ctx.stroke();
253+
};
254+
255+
async function submit() {
256+
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
257+
let response = await fetch('/article/fetch-basics/post/image', {
258+
method: 'POST',
259+
body: blob
260+
});
261+
let result = await response.json();
262+
alert(result.message);
263+
}
264+
265+
</script>
266+
</body>
267+
```
268+
269+
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`).
270+
271+
The `submit()` function can be rewritten without `async/await` like this:
272+
273+
```js
274+
function submit() {
275+
canvasElem.toBlob(function(blob) {
276+
fetch('/article/fetch-basics/post/image', {
277+
method: 'POST',
278+
body: blob
279+
})
280+
.then(response => response.json())
281+
.then(result => alert(JSON.stringify(result, null, 2)))
282+
}, 'image/png');
283+
}
284+
```
285+
286+
## Custom FormData with image
287+
288+
In practice though, it's often more convenient to send an image as a part of the form, with additional fields, such as "name" and other metadata.
289+
290+
Also, servers are usually more suited to accept multipart-encoded forms, rather than raw binary data.
291+
292+
```html run autorun height="90"
293+
<body style="margin:0">
294+
<canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>
295+
296+
<input type="button" value="Submit" onclick="submit()">
297+
298+
<script>
299+
canvasElem.onmousemove = function(e) {
300+
let ctx = canvasElem.getContext('2d');
301+
ctx.lineTo(e.clientX, e.clientY);
302+
ctx.stroke();
303+
};
304+
305+
async function submit() {
306+
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
307+
308+
*!*
309+
let formData = new FormData();
310+
formData.append("name", "myImage");
311+
formData.append("image", blob);
312+
*/!*
313+
314+
let response = await fetch('/article/fetch-basics/post/image-form', {
315+
method: 'POST',
316+
body: formData
317+
});
318+
let result = await response.json();
319+
alert(result.message);
320+
}
321+
322+
</script>
323+
</body>
324+
```
325+
326+
Now, from the server standpoint, the image is a "file" in the form.
327+
328+
## Summary
329+
330+
A typical fetch request consists of two `awaits`:
331+
332+
```js
333+
let response = await fetch(url, options); // resolves with response headers
334+
let result = await response.json(); // read body as json
335+
```
336+
337+
Or, promise-style:
338+
```js
339+
fetch(url, options)
340+
.then(response => response.json())
341+
.then(result => /* process result */)
342+
```
343+
344+
Response properties:
345+
- `response.status` -- HTTP code of the response,
346+
- `response.ok` -- `true` is the status is 200-299.
347+
- `response.headers` -- Map-like object with HTTP headers.
348+
349+
Methods to get response body:
350+
- **`response.json()`** -- parse the response as JSON object,
351+
- **`response.text()`** -- return the response as text,
352+
- **`response.formData()`** -- return the response as FormData object (form/multipart encoding),
353+
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
354+
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data),
355+
356+
Fetch options so far:
357+
- `method` -- HTTP-method,
358+
- `headers` -- an object with request headers (not any header is allowed),
359+
- `body` -- string/FormData/BufferSource/Blob/UrlSearchParams data to submit.
360+
361+
In the next chapters we'll see more options and use cases.
+4
Loading

0 commit comments

Comments
 (0)