fetch draft
This commit is contained in:
parent
440f4086d4
commit
66fa857090
18 changed files with 54282 additions and 0 deletions
261
7-network/1-fetch/article.md
Normal file
261
7-network/1-fetch/article.md
Normal file
|
@ -0,0 +1,261 @@
|
|||
|
||||
# Fetch
|
||||
|
||||
Method `fetch()` is the modern way of sending requests over HTTP.
|
||||
|
||||
Is evolved for several years and continues to improve, right now its support is pretty solid among browsers.
|
||||
|
||||
The basic syntax is:
|
||||
|
||||
```js
|
||||
let promise = fetch(url, [params])
|
||||
```
|
||||
|
||||
- **`url`** -- the URL to access.
|
||||
- **`params`** -- optional parameters: method, headers etc.
|
||||
|
||||
The browser starts the request right away and returns a `promise`.
|
||||
|
||||
Accepting a response is usually a two-step procedure.
|
||||
|
||||
**The `promise` resolves as soon as the server responded with headers.**
|
||||
|
||||
So we can access the headers, we know HTTP status, whether the response is successful, but don't have the body yet.
|
||||
|
||||
We need to wait for the response body additionally, like this:
|
||||
|
||||
```js run async
|
||||
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits');
|
||||
*!*
|
||||
let commits = await response.json();
|
||||
*/!*
|
||||
alert(commits[0].author.login);
|
||||
```
|
||||
|
||||
Or, using pure promises:
|
||||
|
||||
```js run
|
||||
fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits')
|
||||
.then(response => response.json())
|
||||
.then(commits => alert(commits[0].author.login));
|
||||
```
|
||||
|
||||
A `fetch` resolves with `response` -- an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class.
|
||||
|
||||
The main response properties are:
|
||||
- **`ok`** -- boolean, `true` if the HTTP status code is 200-299.
|
||||
- **`status`** -- HTTP status code.
|
||||
- **`headers`** -- HTTP headers, a Map-like object.
|
||||
|
||||
## How to get headers?
|
||||
|
||||
We can iterate over headers the same way as over a `Map`:
|
||||
|
||||
```js run async
|
||||
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits');
|
||||
|
||||
// have headers already
|
||||
for (let [key, value] of response.headers) {
|
||||
alert(`${key} = ${value}`);
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
// wait for the body
|
||||
let json = await response.json();
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
## How to get response?
|
||||
|
||||
`Response` allows to access the body in multiple formats, using following promises:
|
||||
|
||||
- **`json()`** -- parse as JSON object,
|
||||
- **`text()`** -- as text,
|
||||
- **`formData()`** -- as formData (form/multipart encoding),
|
||||
- **`blob()`** -- as Blob (for binary data),
|
||||
- **`arrayBuffer()`** -- as ArrayBuffer (for binary data),
|
||||
- `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk.
|
||||
|
||||
We already saw how to get the response as json.
|
||||
|
||||
As text:
|
||||
```js
|
||||
let text = await response.text();
|
||||
```
|
||||
|
||||
For the binary example, let's download an image and show it:
|
||||
|
||||
```js async run
|
||||
let response = await fetch('/article/fetch/logo-fetch.svg');
|
||||
|
||||
*!*
|
||||
let blob = await response.blob(); // download as Blob object
|
||||
*/!*
|
||||
|
||||
// create <img> with it
|
||||
let img = document.createElement('img');
|
||||
img.src = URL.createObjectURL(blob);
|
||||
|
||||
// show it for 2 seconds
|
||||
document.body.append(img);
|
||||
img.style = 'position:fixed;top:10px;left:10px;width:100px';
|
||||
setTimeout(() => img.remove(), 2000);
|
||||
```
|
||||
|
||||
## The full syntax
|
||||
|
||||
The second argument provides a lot of flexibility to `fetch` syntax.
|
||||
|
||||
Here's the full list of possible options with default values (alternatives commented out):
|
||||
|
||||
```js
|
||||
let promise = fetch(url, {
|
||||
method: "GET", // POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
"Content-Type": "text/plain;charset=UTF-8"
|
||||
},
|
||||
destination: "", // audio, audioworklet, document, embed, font...
|
||||
referrer: "about:client", // "" for no-referrer, or an url from the current origin
|
||||
referrerPolicy: "", // no-referrer, no-referrer-when-downgrade, same-origin...
|
||||
mode: "cors", // same-origin, no-cors, navigate, or websocket
|
||||
credentials: "same-origin", // omit, include
|
||||
cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached
|
||||
redirect: "follow", // manual, error
|
||||
integrity: "" // a hash, like "sha256-abcdef1234567890"
|
||||
keepalive: false // true
|
||||
body: "string" // FormData, Blob, BufferSource, or URLSearchParams
|
||||
})
|
||||
|
||||
|
||||
|
||||
## How to track progress?
|
||||
|
||||
To track download progress, we need to use `response.body`.
|
||||
|
||||
It's a "readable stream" - a special object that provides access chunk-by-chunk.
|
||||
|
||||
Here's the code to do this:
|
||||
|
||||
```js
|
||||
const reader = response.body.getReader();
|
||||
|
||||
while(true) {
|
||||
// done is true for the last chunk
|
||||
// value is Uint8Array of bytes
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (chunk.done) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`Received ${chunk.value.length} bytes`)
|
||||
}
|
||||
```
|
||||
|
||||
We do the infinite loop, while `await reader.read()` returns response chunks.
|
||||
|
||||
A chunk has two properties:
|
||||
- **`done`** -- true when the reading is complete.
|
||||
- **`value`** -- a typed array of bytes: `Uint8Array`.
|
||||
|
||||
The full code to get response and log the progress:
|
||||
|
||||
```js run async
|
||||
// Step 1: start the request and obtain a reader
|
||||
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits?per_page=100');
|
||||
|
||||
const reader = response.body.getReader();
|
||||
|
||||
// Step 2: get total length
|
||||
const contentLength = +response.headers.get('Content-Length');
|
||||
|
||||
// Step 3: read the data
|
||||
let receivedLength = 0;
|
||||
let chunks = [];
|
||||
while(true) {
|
||||
const {done, value} = await reader.read();
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(value);
|
||||
receivedLength += value.length;
|
||||
|
||||
console.log(`Received ${receivedLength} of ${contentLength}`)
|
||||
}
|
||||
|
||||
// Step 4: join chunks into result
|
||||
let chunksAll = new Uint8Array(receivedLength); // (4.1)
|
||||
let position = 0;
|
||||
for(let chunk of chunks) {
|
||||
chunksAll.set(chunk, position); // (4.2)
|
||||
position += chunk.length;
|
||||
}
|
||||
|
||||
// Step 5: decode
|
||||
let result = new TextDecoder("utf-8").decode(chunksMerged);
|
||||
let commits = JSON.parse(result);
|
||||
|
||||
// We're done!
|
||||
alert(commits[0].author.login);
|
||||
```
|
||||
|
||||
Let's explain that step-by-step:
|
||||
|
||||
1. We perform `fetch` as usual, but instead of calling `response.json()`, we obtain a stream reader `response.body.getReader()`.
|
||||
|
||||
Please note, we can't use both these methods to read the same response. Either use a reader or a response method to get the result.
|
||||
2. Prior to reading, we can figure out the full response length by its `Content-Length` header.
|
||||
|
||||
It may be absent for cross-domain requests (as in the example) and, well, technically a server doesn't have to set it. But usually it's at place.
|
||||
3. Now `await reader.read()` until it's done.
|
||||
|
||||
We gather the `chunks` in the array. That's important, because after the response is consumed, we won't be able to "re-read" it using `response.json()` or another way (you can try, there'll be an error).
|
||||
4. At the end, we have `chunks` -- an array of `Uint8Array` byte chunks. We need to join them into a single result. Unfortunately, there's no single method that concatenates those.
|
||||
1. We create `new Uint8Array(receivedLength)` -- a same-type array with the combined length.
|
||||
2. Then use `.set(chunk, position)` method that copies each `chunk` at the given `position` (one by one) in the resulting array.
|
||||
5. We have the result in `chunksAll`. It's a byte array though, not a string.
|
||||
|
||||
To create a string, we need to interpret these bytes. The built-in `TextEncoder` does exactly that. Then we can `JSON.parse` it.
|
||||
|
||||
What if it were a binary file? We could make a blob of it:
|
||||
```js
|
||||
let blob = new Blob([chunksAll.buffer]);
|
||||
```
|
||||
|
||||
```js run async
|
||||
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits?per_page=100');
|
||||
|
||||
const contentLength = +response.headers.get('Content-Length');
|
||||
|
||||
const reader = response.body.getReader();
|
||||
|
||||
let receivedLength = 0;
|
||||
let chunks = [];
|
||||
while(true) {
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (chunk.done) {
|
||||
console.log("done!");
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(chunk.value);
|
||||
|
||||
receivedLength += chunk.value.length;
|
||||
console.log(`${receivedLength}/${contentLength} received`)
|
||||
}
|
||||
|
||||
let chunksMerged = new Uint8Array(receivedLength);
|
||||
let length = 0;
|
||||
for(let chunk of chunks) {
|
||||
chunksMerged.set(chunk, length);
|
||||
length += chunk.length;
|
||||
}
|
||||
|
||||
let result = new TextDecoder("utf-8").decode(chunksMerged);
|
||||
console.log(JSON.parse(result));
|
||||
```
|
4
7-network/1-fetch/logo-fetch.svg
Normal file
4
7-network/1-fetch/logo-fetch.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="45" fill="#fff" stroke="#3c790a" stroke-width="10"/>
|
||||
<path d="m34,55a60,60,0,0,0,20,-20a6,10,0,0,1,13,-1a10,6,0,0,1,-1,13a60,60,0,0,0,-20,20a6,10,0,0,1,-13,1a10,6,0,0,1,1,-13" fill="#3c790a"/>
|
||||
</svg>
|
After Width: | Height: | Size: 290 B |
36
7-network/1-fetch/progress.view/index.html
Normal file
36
7-network/1-fetch/progress.view/index.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<!doctype html>
|
||||
<script>
|
||||
(async () {
|
||||
|
||||
const response = await fetch('long.txt');
|
||||
const reader = response.body.getReader();
|
||||
|
||||
const contentLength = +response.headers.get('Content-Length');
|
||||
let receivedLength = 0;
|
||||
let chunks = [];
|
||||
while(true) {
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (chunk.done) {
|
||||
console.log("done!");
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(chunk.value);
|
||||
receivedLength += chunk.value.length;
|
||||
console.log(`${receivedLength}/${contentLength} received`)
|
||||
}
|
||||
|
||||
|
||||
let chunksMerged = new Uint8Array(receivedLength);
|
||||
let length = 0;
|
||||
for(let chunk of chunks) {
|
||||
chunksMerged.set(chunk, length);
|
||||
length += chunk.length;
|
||||
}
|
||||
|
||||
let result = new TextDecoder("utf-8").decode(chunksMerged);
|
||||
console.log(result);
|
||||
})();
|
||||
|
||||
</script>
|
26973
7-network/1-fetch/progress.view/long.txt
Normal file
26973
7-network/1-fetch/progress.view/long.txt
Normal file
File diff suppressed because it is too large
Load diff
0
7-network/1-xmlhttprequest/phones.json → 7-network/10-xmlhttprequest/phones.json
Executable file → Normal file
0
7-network/1-xmlhttprequest/phones.json → 7-network/10-xmlhttprequest/phones.json
Executable file → Normal file
4
7-network/2-url/article.md
Normal file
4
7-network/2-url/article.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
# Fetch
|
||||
|
||||
[codetabs src="progress"]
|
31
7-network/2-url/progress.view/index.html
Normal file
31
7-network/2-url/progress.view/index.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<script>
|
||||
|
||||
async function init() {
|
||||
const response = await fetch('long.txt');
|
||||
const reader = response.body.getReader();
|
||||
|
||||
const contentLength = +response.headers.get('Content-Length');
|
||||
let receivedLength = 0;
|
||||
|
||||
while(true) {
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (chunk.done) {
|
||||
console.log("done!");
|
||||
break;
|
||||
}
|
||||
|
||||
receivedLength += chunk.value.length;
|
||||
console.log(`${receivedLength}/${contentLength} received`)
|
||||
}
|
||||
|
||||
let result = await response.text();
|
||||
console.log(result);
|
||||
//const chunkCount = await read(reader);
|
||||
//console.log(`Finished! Received ${chunkCount} chunks.`);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
</script>
|
26973
7-network/2-url/progress.view/long.txt
Normal file
26973
7-network/2-url/progress.view/long.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue