From 22320899dee1fe89e97c6d3fd76e097cd6a4e8c2 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 10:05:12 +0800 Subject: [PATCH 01/11] Sync with upstream --- .../01-fetch-users/_js.view/solution.js | 24 ++ .../01-fetch-users/_js.view/source.js | 4 + .../01-fetch/01-fetch-users/_js.view/test.js | 10 + 5-network/01-fetch/01-fetch-users/solution.md | 41 +++ 5-network/01-fetch/01-fetch-users/task.md | 12 + 5-network/01-fetch/article.md | 302 ++++++++++++++++++ 5-network/01-fetch/logo-fetch.svg | 4 + .../01-fetch-users/_js.view/solution.js | 24 ++ .../01-fetch-users/_js.view/source.js | 4 + .../post.view/01-fetch-users/_js.view/test.js | 10 + .../post.view/01-fetch-users/solution.md | 41 +++ .../01-fetch/post.view/01-fetch-users/task.md | 12 + 5-network/01-fetch/post.view/article.md | 302 ++++++++++++++++++ 5-network/01-fetch/post.view/logo-fetch.svg | 4 + 5-network/01-fetch/post.view/server.js | 34 ++ 15 files changed, 828 insertions(+) create mode 100644 5-network/01-fetch/01-fetch-users/_js.view/solution.js create mode 100644 5-network/01-fetch/01-fetch-users/_js.view/source.js create mode 100644 5-network/01-fetch/01-fetch-users/_js.view/test.js create mode 100644 5-network/01-fetch/01-fetch-users/solution.md create mode 100644 5-network/01-fetch/01-fetch-users/task.md create mode 100644 5-network/01-fetch/article.md create mode 100644 5-network/01-fetch/logo-fetch.svg create mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js create mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js create mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js create mode 100644 5-network/01-fetch/post.view/01-fetch-users/solution.md create mode 100644 5-network/01-fetch/post.view/01-fetch-users/task.md create mode 100644 5-network/01-fetch/post.view/article.md create mode 100644 5-network/01-fetch/post.view/logo-fetch.svg create mode 100644 5-network/01-fetch/post.view/server.js diff --git a/5-network/01-fetch/01-fetch-users/_js.view/solution.js b/5-network/01-fetch/01-fetch-users/_js.view/solution.js new file mode 100644 index 0000000000..da448b47a1 --- /dev/null +++ b/5-network/01-fetch/01-fetch-users/_js.view/solution.js @@ -0,0 +1,24 @@ + +async function getUsers(names) { + let jobs = []; + + for(let name of names) { + let job = fetch(`https://api.github.com/users/${name}`).then( + successResponse => { + if (successResponse.status != 200) { + return null; + } else { + return successResponse.json(); + } + }, + failResponse => { + return null; + } + ); + jobs.push(job); + } + + let results = await Promise.all(jobs); + + return results; +} diff --git a/5-network/01-fetch/01-fetch-users/_js.view/source.js b/5-network/01-fetch/01-fetch-users/_js.view/source.js new file mode 100644 index 0000000000..0c62e7bb57 --- /dev/null +++ b/5-network/01-fetch/01-fetch-users/_js.view/source.js @@ -0,0 +1,4 @@ + +async function getUsers(names) { + /* your code */ +} diff --git a/5-network/01-fetch/01-fetch-users/_js.view/test.js b/5-network/01-fetch/01-fetch-users/_js.view/test.js new file mode 100644 index 0000000000..95eaf876e0 --- /dev/null +++ b/5-network/01-fetch/01-fetch-users/_js.view/test.js @@ -0,0 +1,10 @@ +describe("getUsers", function() { + + it("gets users from GitHub", async function() { + let users = await getUsers(['iliakan', 'remy', 'no.such.users']); + assert.equal(users[0].login, 'iliakan'); + assert.equal(users[1].login, 'remy'); + assert.equal(users[2], null); + }); + +}); diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md new file mode 100644 index 0000000000..6fa6fbb1c7 --- /dev/null +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -0,0 +1,41 @@ + +To fetch a user we need: + +1. `fetch('https://api.github.com/users/USERNAME')`. +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. + +So here's the code: + +```js demo +async function getUsers(names) { + let jobs = []; + + for(let name of names) { + let job = fetch(`https://api.github.com/users/${name}`).then( + successResponse => { + if (successResponse.status != 200) { + return null; + } else { + return successResponse.json(); + } + }, + failResponse => { + return null; + } + ); + jobs.push(job); + } + + let results = await Promise.all(jobs); + + return results; +} +``` + +Please note: `.then` call is attached directly to `fetch`, so that when we have the response, it doesn't wait for other fetches, but starts to read `.json()` immediately. + +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`. diff --git a/5-network/01-fetch/01-fetch-users/task.md b/5-network/01-fetch/01-fetch-users/task.md new file mode 100644 index 0000000000..92cdd19f2f --- /dev/null +++ b/5-network/01-fetch/01-fetch-users/task.md @@ -0,0 +1,12 @@ +# Fetch users from GitHub + +Create an async function `getUsers(names)`, that gets an array of GitHub logins, fetche the users from GitHub and returns an array of GitHub users. + +The GitHub url with user informaiton for the given `USERNAME` is: `https://api.github.com/users/USERNAME`. + +There's a test example in the sandbox. + +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. +2. If any request fails, or if there's no such user, the function should return `null` in the resulting array. diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md new file mode 100644 index 0000000000..194ad9f651 --- /dev/null +++ b/5-network/01-fetch/article.md @@ -0,0 +1,302 @@ + +# Fetch + +JavaScript can send network requests to the server and load new information whenever is needed. + +For example, we can: + +- Submit an order, +- Load user information, +- Receive latest updates from the server, +- ...etc. + +...And all of that without reloading the page! + +There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. + +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 basic syntax is: + +```js +let promise = fetch(url, [options]) +``` + +- **`url`** -- the URL to access. +- **`options`** -- optional parameters: method, headers etc. + +The browser starts the request right away and returns a `promise`. + +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.** + +So 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. + +We can see them in response properties: + +- **`ok`** -- boolean, `true` if the HTTP status code is 200-299. +- **`status`** -- HTTP status code. + +For example: + +```js +let response = await fetch(url); + +if (response.ok) { // if HTTP-status is 200-299 + // get the response body (see below) + let json = await response.json(); +} else { + alert("HTTP-Error: " + response.status); +} +``` + +**Second, to get the response body, we need to use an additional method call.** + +`Response` provides multiple promise-based methods to access the body in various formats: + +- **`response.json()`** -- parse the response as JSON object, +- **`response.text()`** -- return the response as text, +- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), +- **`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), +- 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: + +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +*!* +let commits = await response.json(); // read response body and parse as JSON +*/!* + +alert(commits[0].author.login); +``` + +Or, the same without `await`, using pure promises syntax: + +```js run +fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') + .then(response => response.json()) + .then(commits => alert(commits[0].author.login)); +``` + +To get the text, `await response.text()` instead of `.json()`: +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +let text = await response.text(); // read response body as text + +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): + +```js async run +let response = await fetch('/article/fetch/logo-fetch.svg'); + +*!* +let blob = await response.blob(); // download as Blob object +*/!* + +// create for it +let img = document.createElement('img'); +img.style = 'position:fixed;top:10px;left:10px;width:100px'; +document.body.append(img); + +// show it +img.src = URL.createObjectURL(blob); + +setTimeout(() => { // hide after three seconds + img.remove(); + URL.revokeObjectURL(img.src); +}, 3000); +``` + +````warn +We can choose only one body-parsing method. + +If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed. + +```js +let text = await response.text(); // response body consumed +let parsed = await response.json(); // fails (already consumed) +```` + +## Headers + +There's a Map-like headers object in `response.headers`. + +We can get individual headers or iterate over them: + +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +// get one header +alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 + +// iterate over all headers +for (let [key, value] of response.headers) { + alert(`${key} = ${value}`); +} +``` + +To set a header, we can use the `headers` option, like this: + +```js +let response = fetch(protectedUrl, { + headers: { + Authentication: 'abcdef' + } +}); +``` + +...But there's a list of [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name) that we can't set: + +- `Accept-Charset`, `Accept-Encoding` +- `Access-Control-Request-Headers` +- `Access-Control-Request-Method` +- `Connection` +- `Content-Length` +- `Cookie`, `Cookie2` +- `Date` +- `DNT` +- `Expect` +- `Host` +- `Keep-Alive` +- `Origin` +- `Referer` +- `TE` +- `Trailer` +- `Transfer-Encoding` +- `Upgrade` +- `Via` +- `Proxy-*` +- `Sec-*` + +These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser. + +## POST requests + +To make a `POST` request, or a request with another method, we need to use `fetch` options: + +- **`method`** -- HTTP-method, e.g. `POST`, +- **`body`** -- one of: + - a string (e.g. JSON), + - `FormData` object, to submit the data as `form/multipart`, + - `Blob`/`BufferSource` to send binary data, + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. + +For example, this code submits `user` object as JSON: + +```js run async +let user = { + name: 'John', + surname: 'Smith' +}; + +*!* +let response = await fetch('/article/fetch/post/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(user) +}); +*/!* + +let result = await response.json(); +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. + +## Sending an image + +We can also submit binary data directly using `Blob` or `BufferSource`. + +For example, here's a `` where we can draw by moving a mouse. A click on the "submit" button sends the image to server: + +```html run autorun height="90" + + + + + + + +``` + +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`). + +The `submit()` function can be rewritten without `async/await` like this: + +```js +function submit() { + canvasElem.toBlob(function(blob) { + fetch('/article/fetch/post/image', { + method: 'POST', + body: blob + }) + .then(response => response.json()) + .then(result => alert(JSON.stringify(result, null, 2))) + }, 'image/png'); +} +``` + +## Summary + +A typical fetch request consists of two `await` calls: + +```js +let response = await fetch(url, options); // resolves with response headers +let result = await response.json(); // read body as json +``` + +Or, promise-style: +```js +fetch(url, options) + .then(response => response.json()) + .then(result => /* process result */) +``` + +Response properties: +- `response.status` -- HTTP code of the response, +- `response.ok` -- `true` is the status is 200-299. +- `response.headers` -- Map-like object with HTTP headers. + +Methods to get response body: +- **`response.json()`** -- parse the response as JSON object, +- **`response.text()`** -- return the response as text, +- **`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.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), + +Fetch options so far: +- `method` -- HTTP-method, +- `headers` -- an object with request headers (not any header is allowed), +- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. + +In the next chapters we'll see more options and use cases of `fetch`. diff --git a/5-network/01-fetch/logo-fetch.svg b/5-network/01-fetch/logo-fetch.svg new file mode 100644 index 0000000000..5a58b209b5 --- /dev/null +++ b/5-network/01-fetch/logo-fetch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js new file mode 100644 index 0000000000..da448b47a1 --- /dev/null +++ b/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js @@ -0,0 +1,24 @@ + +async function getUsers(names) { + let jobs = []; + + for(let name of names) { + let job = fetch(`https://api.github.com/users/${name}`).then( + successResponse => { + if (successResponse.status != 200) { + return null; + } else { + return successResponse.json(); + } + }, + failResponse => { + return null; + } + ); + jobs.push(job); + } + + let results = await Promise.all(jobs); + + return results; +} diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js new file mode 100644 index 0000000000..0c62e7bb57 --- /dev/null +++ b/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js @@ -0,0 +1,4 @@ + +async function getUsers(names) { + /* your code */ +} diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js new file mode 100644 index 0000000000..95eaf876e0 --- /dev/null +++ b/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js @@ -0,0 +1,10 @@ +describe("getUsers", function() { + + it("gets users from GitHub", async function() { + let users = await getUsers(['iliakan', 'remy', 'no.such.users']); + assert.equal(users[0].login, 'iliakan'); + assert.equal(users[1].login, 'remy'); + assert.equal(users[2], null); + }); + +}); diff --git a/5-network/01-fetch/post.view/01-fetch-users/solution.md b/5-network/01-fetch/post.view/01-fetch-users/solution.md new file mode 100644 index 0000000000..6fa6fbb1c7 --- /dev/null +++ b/5-network/01-fetch/post.view/01-fetch-users/solution.md @@ -0,0 +1,41 @@ + +To fetch a user we need: + +1. `fetch('https://api.github.com/users/USERNAME')`. +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. + +So here's the code: + +```js demo +async function getUsers(names) { + let jobs = []; + + for(let name of names) { + let job = fetch(`https://api.github.com/users/${name}`).then( + successResponse => { + if (successResponse.status != 200) { + return null; + } else { + return successResponse.json(); + } + }, + failResponse => { + return null; + } + ); + jobs.push(job); + } + + let results = await Promise.all(jobs); + + return results; +} +``` + +Please note: `.then` call is attached directly to `fetch`, so that when we have the response, it doesn't wait for other fetches, but starts to read `.json()` immediately. + +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`. diff --git a/5-network/01-fetch/post.view/01-fetch-users/task.md b/5-network/01-fetch/post.view/01-fetch-users/task.md new file mode 100644 index 0000000000..92cdd19f2f --- /dev/null +++ b/5-network/01-fetch/post.view/01-fetch-users/task.md @@ -0,0 +1,12 @@ +# Fetch users from GitHub + +Create an async function `getUsers(names)`, that gets an array of GitHub logins, fetche the users from GitHub and returns an array of GitHub users. + +The GitHub url with user informaiton for the given `USERNAME` is: `https://api.github.com/users/USERNAME`. + +There's a test example in the sandbox. + +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. +2. If any request fails, or if there's no such user, the function should return `null` in the resulting array. diff --git a/5-network/01-fetch/post.view/article.md b/5-network/01-fetch/post.view/article.md new file mode 100644 index 0000000000..194ad9f651 --- /dev/null +++ b/5-network/01-fetch/post.view/article.md @@ -0,0 +1,302 @@ + +# Fetch + +JavaScript can send network requests to the server and load new information whenever is needed. + +For example, we can: + +- Submit an order, +- Load user information, +- Receive latest updates from the server, +- ...etc. + +...And all of that without reloading the page! + +There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. + +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 basic syntax is: + +```js +let promise = fetch(url, [options]) +``` + +- **`url`** -- the URL to access. +- **`options`** -- optional parameters: method, headers etc. + +The browser starts the request right away and returns a `promise`. + +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.** + +So 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. + +We can see them in response properties: + +- **`ok`** -- boolean, `true` if the HTTP status code is 200-299. +- **`status`** -- HTTP status code. + +For example: + +```js +let response = await fetch(url); + +if (response.ok) { // if HTTP-status is 200-299 + // get the response body (see below) + let json = await response.json(); +} else { + alert("HTTP-Error: " + response.status); +} +``` + +**Second, to get the response body, we need to use an additional method call.** + +`Response` provides multiple promise-based methods to access the body in various formats: + +- **`response.json()`** -- parse the response as JSON object, +- **`response.text()`** -- return the response as text, +- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), +- **`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), +- 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: + +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +*!* +let commits = await response.json(); // read response body and parse as JSON +*/!* + +alert(commits[0].author.login); +``` + +Or, the same without `await`, using pure promises syntax: + +```js run +fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') + .then(response => response.json()) + .then(commits => alert(commits[0].author.login)); +``` + +To get the text, `await response.text()` instead of `.json()`: +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +let text = await response.text(); // read response body as text + +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): + +```js async run +let response = await fetch('/article/fetch/logo-fetch.svg'); + +*!* +let blob = await response.blob(); // download as Blob object +*/!* + +// create for it +let img = document.createElement('img'); +img.style = 'position:fixed;top:10px;left:10px;width:100px'; +document.body.append(img); + +// show it +img.src = URL.createObjectURL(blob); + +setTimeout(() => { // hide after three seconds + img.remove(); + URL.revokeObjectURL(img.src); +}, 3000); +``` + +````warn +We can choose only one body-parsing method. + +If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed. + +```js +let text = await response.text(); // response body consumed +let parsed = await response.json(); // fails (already consumed) +```` + +## Headers + +There's a Map-like headers object in `response.headers`. + +We can get individual headers or iterate over them: + +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +// get one header +alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 + +// iterate over all headers +for (let [key, value] of response.headers) { + alert(`${key} = ${value}`); +} +``` + +To set a header, we can use the `headers` option, like this: + +```js +let response = fetch(protectedUrl, { + headers: { + Authentication: 'abcdef' + } +}); +``` + +...But there's a list of [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name) that we can't set: + +- `Accept-Charset`, `Accept-Encoding` +- `Access-Control-Request-Headers` +- `Access-Control-Request-Method` +- `Connection` +- `Content-Length` +- `Cookie`, `Cookie2` +- `Date` +- `DNT` +- `Expect` +- `Host` +- `Keep-Alive` +- `Origin` +- `Referer` +- `TE` +- `Trailer` +- `Transfer-Encoding` +- `Upgrade` +- `Via` +- `Proxy-*` +- `Sec-*` + +These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser. + +## POST requests + +To make a `POST` request, or a request with another method, we need to use `fetch` options: + +- **`method`** -- HTTP-method, e.g. `POST`, +- **`body`** -- one of: + - a string (e.g. JSON), + - `FormData` object, to submit the data as `form/multipart`, + - `Blob`/`BufferSource` to send binary data, + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. + +For example, this code submits `user` object as JSON: + +```js run async +let user = { + name: 'John', + surname: 'Smith' +}; + +*!* +let response = await fetch('/article/fetch/post/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(user) +}); +*/!* + +let result = await response.json(); +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. + +## Sending an image + +We can also submit binary data directly using `Blob` or `BufferSource`. + +For example, here's a `` where we can draw by moving a mouse. A click on the "submit" button sends the image to server: + +```html run autorun height="90" + + + + + + + +``` + +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`). + +The `submit()` function can be rewritten without `async/await` like this: + +```js +function submit() { + canvasElem.toBlob(function(blob) { + fetch('/article/fetch/post/image', { + method: 'POST', + body: blob + }) + .then(response => response.json()) + .then(result => alert(JSON.stringify(result, null, 2))) + }, 'image/png'); +} +``` + +## Summary + +A typical fetch request consists of two `await` calls: + +```js +let response = await fetch(url, options); // resolves with response headers +let result = await response.json(); // read body as json +``` + +Or, promise-style: +```js +fetch(url, options) + .then(response => response.json()) + .then(result => /* process result */) +``` + +Response properties: +- `response.status` -- HTTP code of the response, +- `response.ok` -- `true` is the status is 200-299. +- `response.headers` -- Map-like object with HTTP headers. + +Methods to get response body: +- **`response.json()`** -- parse the response as JSON object, +- **`response.text()`** -- return the response as text, +- **`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.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), + +Fetch options so far: +- `method` -- HTTP-method, +- `headers` -- an object with request headers (not any header is allowed), +- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. + +In the next chapters we'll see more options and use cases of `fetch`. diff --git a/5-network/01-fetch/post.view/logo-fetch.svg b/5-network/01-fetch/post.view/logo-fetch.svg new file mode 100644 index 0000000000..5a58b209b5 --- /dev/null +++ b/5-network/01-fetch/post.view/logo-fetch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/5-network/01-fetch/post.view/server.js b/5-network/01-fetch/post.view/server.js new file mode 100644 index 0000000000..b55870b2aa --- /dev/null +++ b/5-network/01-fetch/post.view/server.js @@ -0,0 +1,34 @@ +const Koa = require('koa'); +const app = new Koa(); +const bodyParser = require('koa-bodyparser'); +const getRawBody = require('raw-body') +const Router = require('koa-router'); + +let router = new Router(); + +router.post('/user', async (ctx) => { + ctx.body = { + message: "User saved." + }; +}); + +router.post('/image', async (ctx) => { + let body = await getRawBody(ctx.req, { + limit: '1mb' + }); + ctx.body = { + message: `Image saved, size:${body.length}.` + }; +}); + +app + .use(bodyParser()) + .use(router.routes()) + .use(router.allowedMethods()); + + +if (!module.parent) { + http.createServer(app.callback()).listen(8080); +} else { + exports.accept = app.callback(); +} From 157c6b1ea696f4807ddbc270d9e4fdb5dade6d9a Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 10:08:47 +0800 Subject: [PATCH 02/11] Translate 5-network/01-fetch/01-fetch-users/_js.view/source.js --- 5-network/01-fetch/01-fetch-users/_js.view/source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/01-fetch/01-fetch-users/_js.view/source.js b/5-network/01-fetch/01-fetch-users/_js.view/source.js index 0c62e7bb57..0e7895050c 100644 --- a/5-network/01-fetch/01-fetch-users/_js.view/source.js +++ b/5-network/01-fetch/01-fetch-users/_js.view/source.js @@ -1,4 +1,4 @@ async function getUsers(names) { - /* your code */ + /* 你的代码 */ } From 8b286f1e2d5918ac85c2e7170109641fc52e8caa Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 10:32:03 +0800 Subject: [PATCH 03/11] Translate task.md --- 5-network/01-fetch/01-fetch-users/task.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/5-network/01-fetch/01-fetch-users/task.md b/5-network/01-fetch/01-fetch-users/task.md index 92cdd19f2f..d8858eaba0 100644 --- a/5-network/01-fetch/01-fetch-users/task.md +++ b/5-network/01-fetch/01-fetch-users/task.md @@ -1,12 +1,12 @@ -# Fetch users from GitHub +# 从 GitHub fetch 用户信息 -Create an async function `getUsers(names)`, that gets an array of GitHub logins, fetche the users from GitHub and returns an array of GitHub users. +创建能从一组 GitHub 用户数组获取一组数据的异步函数 `getUsers(names)`,从 GitHub 获取用户信息并返回一组 GitHub 用户信息的数组。 -The GitHub url with user informaiton for the given `USERNAME` is: `https://api.github.com/users/USERNAME`. +给定 `USERNAME` 的用户信息的 GitHub 网址是:`https://api.github.com/users/USERNAME`。 -There's a test example in the sandbox. +Sandbox 里有一个测试范例。 -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. -2. If any request fails, or if there's no such user, the function should return `null` in the resulting array. +1. 每一个用户都应该有一个 `fetch` 请求,并且请求是独立的不用彼此等待。因此数据能尽快获取到。 +2. 如果任意一个请求失败了,或者没有这个用户,函数应该返回 `null` 到最终结果数组中。 From 54614f61c515bc2550cdcd80a615e9a70f2a0a8f Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 10:32:18 +0800 Subject: [PATCH 04/11] Translate solution.md --- 5-network/01-fetch/01-fetch-users/solution.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index 6fa6fbb1c7..16878ae7ff 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -1,12 +1,12 @@ -To fetch a user we need: +要获取一个用户,我们需要: 1. `fetch('https://api.github.com/users/USERNAME')`. -2. If the response has status `200`, call `.json()` to read the JS object. +2. 如果相应状态码是 `200` 就调用 `.json()` 来读取 JS 对象。 -If a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray. +如果 `fetch` 失败,或者相应状态码不是 200,我们只要返回 `null` 到最终结果数组中就行了。 -So here's the code: +下面是参考代码: ```js demo async function getUsers(names) { @@ -34,8 +34,8 @@ async function getUsers(names) { } ``` -Please note: `.then` call is attached directly to `fetch`, so that when we have the response, it doesn't wait for other fetches, but starts to read `.json()` immediately. +请注意:`.then` 紧跟在 `fetch` 后面,因此当我们有了响应数据,它就不会等待其他的 fetch 请求而直接开始读取 `.json()`。 -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. +如果我们使用 `await Promise.all(names.map(name => fetch(...)))`,且在其结果上调用 `.json()` 方法,那么它将会等到所有 fetch 都获取到响应数据才开始解析。通过直接添加 `.json()` 到每个 `fetch`,我们就能确保每个 fetch 都能读取 JSON 数据而不用等待其他 fetch 请求。 -That's an example of how low-level `Promise` API can still be useful even if we mainly use `async/await`. +这个例子表明,即使我们主要使用 `async/await`,低级(low-level)的 `Promise` API 仍然很有用。 From 04953fdc219f1356edf840b5b343317aa9658a34 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 10:33:48 +0800 Subject: [PATCH 05/11] Delete some files --- .../01-fetch-users/_js.view/solution.js | 24 -- .../01-fetch-users/_js.view/source.js | 4 - .../post.view/01-fetch-users/_js.view/test.js | 10 - .../post.view/01-fetch-users/solution.md | 41 --- .../01-fetch/post.view/01-fetch-users/task.md | 12 - 5-network/01-fetch/post.view/article.md | 302 ------------------ 5-network/01-fetch/post.view/logo-fetch.svg | 4 - 7 files changed, 397 deletions(-) delete mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js delete mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js delete mode 100644 5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js delete mode 100644 5-network/01-fetch/post.view/01-fetch-users/solution.md delete mode 100644 5-network/01-fetch/post.view/01-fetch-users/task.md delete mode 100644 5-network/01-fetch/post.view/article.md delete mode 100644 5-network/01-fetch/post.view/logo-fetch.svg diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js deleted file mode 100644 index da448b47a1..0000000000 --- a/5-network/01-fetch/post.view/01-fetch-users/_js.view/solution.js +++ /dev/null @@ -1,24 +0,0 @@ - -async function getUsers(names) { - let jobs = []; - - for(let name of names) { - let job = fetch(`https://api.github.com/users/${name}`).then( - successResponse => { - if (successResponse.status != 200) { - return null; - } else { - return successResponse.json(); - } - }, - failResponse => { - return null; - } - ); - jobs.push(job); - } - - let results = await Promise.all(jobs); - - return results; -} diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js deleted file mode 100644 index 0c62e7bb57..0000000000 --- a/5-network/01-fetch/post.view/01-fetch-users/_js.view/source.js +++ /dev/null @@ -1,4 +0,0 @@ - -async function getUsers(names) { - /* your code */ -} diff --git a/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js b/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js deleted file mode 100644 index 95eaf876e0..0000000000 --- a/5-network/01-fetch/post.view/01-fetch-users/_js.view/test.js +++ /dev/null @@ -1,10 +0,0 @@ -describe("getUsers", function() { - - it("gets users from GitHub", async function() { - let users = await getUsers(['iliakan', 'remy', 'no.such.users']); - assert.equal(users[0].login, 'iliakan'); - assert.equal(users[1].login, 'remy'); - assert.equal(users[2], null); - }); - -}); diff --git a/5-network/01-fetch/post.view/01-fetch-users/solution.md b/5-network/01-fetch/post.view/01-fetch-users/solution.md deleted file mode 100644 index 6fa6fbb1c7..0000000000 --- a/5-network/01-fetch/post.view/01-fetch-users/solution.md +++ /dev/null @@ -1,41 +0,0 @@ - -To fetch a user we need: - -1. `fetch('https://api.github.com/users/USERNAME')`. -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. - -So here's the code: - -```js demo -async function getUsers(names) { - let jobs = []; - - for(let name of names) { - let job = fetch(`https://api.github.com/users/${name}`).then( - successResponse => { - if (successResponse.status != 200) { - return null; - } else { - return successResponse.json(); - } - }, - failResponse => { - return null; - } - ); - jobs.push(job); - } - - let results = await Promise.all(jobs); - - return results; -} -``` - -Please note: `.then` call is attached directly to `fetch`, so that when we have the response, it doesn't wait for other fetches, but starts to read `.json()` immediately. - -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`. diff --git a/5-network/01-fetch/post.view/01-fetch-users/task.md b/5-network/01-fetch/post.view/01-fetch-users/task.md deleted file mode 100644 index 92cdd19f2f..0000000000 --- a/5-network/01-fetch/post.view/01-fetch-users/task.md +++ /dev/null @@ -1,12 +0,0 @@ -# Fetch users from GitHub - -Create an async function `getUsers(names)`, that gets an array of GitHub logins, fetche the users from GitHub and returns an array of GitHub users. - -The GitHub url with user informaiton for the given `USERNAME` is: `https://api.github.com/users/USERNAME`. - -There's a test example in the sandbox. - -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. -2. If any request fails, or if there's no such user, the function should return `null` in the resulting array. diff --git a/5-network/01-fetch/post.view/article.md b/5-network/01-fetch/post.view/article.md deleted file mode 100644 index 194ad9f651..0000000000 --- a/5-network/01-fetch/post.view/article.md +++ /dev/null @@ -1,302 +0,0 @@ - -# Fetch - -JavaScript can send network requests to the server and load new information whenever is needed. - -For example, we can: - -- Submit an order, -- Load user information, -- Receive latest updates from the server, -- ...etc. - -...And all of that without reloading the page! - -There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. - -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 basic syntax is: - -```js -let promise = fetch(url, [options]) -``` - -- **`url`** -- the URL to access. -- **`options`** -- optional parameters: method, headers etc. - -The browser starts the request right away and returns a `promise`. - -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.** - -So 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. - -We can see them in response properties: - -- **`ok`** -- boolean, `true` if the HTTP status code is 200-299. -- **`status`** -- HTTP status code. - -For example: - -```js -let response = await fetch(url); - -if (response.ok) { // if HTTP-status is 200-299 - // get the response body (see below) - let json = await response.json(); -} else { - alert("HTTP-Error: " + response.status); -} -``` - -**Second, to get the response body, we need to use an additional method call.** - -`Response` provides multiple promise-based methods to access the body in various formats: - -- **`response.json()`** -- parse the response as JSON object, -- **`response.text()`** -- return the response as text, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), -- **`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), -- 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: - -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -*!* -let commits = await response.json(); // read response body and parse as JSON -*/!* - -alert(commits[0].author.login); -``` - -Or, the same without `await`, using pure promises syntax: - -```js run -fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') - .then(response => response.json()) - .then(commits => alert(commits[0].author.login)); -``` - -To get the text, `await response.text()` instead of `.json()`: -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -let text = await response.text(); // read response body as text - -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): - -```js async run -let response = await fetch('/article/fetch/logo-fetch.svg'); - -*!* -let blob = await response.blob(); // download as Blob object -*/!* - -// create for it -let img = document.createElement('img'); -img.style = 'position:fixed;top:10px;left:10px;width:100px'; -document.body.append(img); - -// show it -img.src = URL.createObjectURL(blob); - -setTimeout(() => { // hide after three seconds - img.remove(); - URL.revokeObjectURL(img.src); -}, 3000); -``` - -````warn -We can choose only one body-parsing method. - -If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed. - -```js -let text = await response.text(); // response body consumed -let parsed = await response.json(); // fails (already consumed) -```` - -## Headers - -There's a Map-like headers object in `response.headers`. - -We can get individual headers or iterate over them: - -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -// get one header -alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 - -// iterate over all headers -for (let [key, value] of response.headers) { - alert(`${key} = ${value}`); -} -``` - -To set a header, we can use the `headers` option, like this: - -```js -let response = fetch(protectedUrl, { - headers: { - Authentication: 'abcdef' - } -}); -``` - -...But there's a list of [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name) that we can't set: - -- `Accept-Charset`, `Accept-Encoding` -- `Access-Control-Request-Headers` -- `Access-Control-Request-Method` -- `Connection` -- `Content-Length` -- `Cookie`, `Cookie2` -- `Date` -- `DNT` -- `Expect` -- `Host` -- `Keep-Alive` -- `Origin` -- `Referer` -- `TE` -- `Trailer` -- `Transfer-Encoding` -- `Upgrade` -- `Via` -- `Proxy-*` -- `Sec-*` - -These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser. - -## POST requests - -To make a `POST` request, or a request with another method, we need to use `fetch` options: - -- **`method`** -- HTTP-method, e.g. `POST`, -- **`body`** -- one of: - - a string (e.g. JSON), - - `FormData` object, to submit the data as `form/multipart`, - - `Blob`/`BufferSource` to send binary data, - - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. - -For example, this code submits `user` object as JSON: - -```js run async -let user = { - name: 'John', - surname: 'Smith' -}; - -*!* -let response = await fetch('/article/fetch/post/user', { - method: 'POST', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(user) -}); -*/!* - -let result = await response.json(); -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. - -## Sending an image - -We can also submit binary data directly using `Blob` or `BufferSource`. - -For example, here's a `` where we can draw by moving a mouse. A click on the "submit" button sends the image to server: - -```html run autorun height="90" - - - - - - - -``` - -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`). - -The `submit()` function can be rewritten without `async/await` like this: - -```js -function submit() { - canvasElem.toBlob(function(blob) { - fetch('/article/fetch/post/image', { - method: 'POST', - body: blob - }) - .then(response => response.json()) - .then(result => alert(JSON.stringify(result, null, 2))) - }, 'image/png'); -} -``` - -## Summary - -A typical fetch request consists of two `await` calls: - -```js -let response = await fetch(url, options); // resolves with response headers -let result = await response.json(); // read body as json -``` - -Or, promise-style: -```js -fetch(url, options) - .then(response => response.json()) - .then(result => /* process result */) -``` - -Response properties: -- `response.status` -- HTTP code of the response, -- `response.ok` -- `true` is the status is 200-299. -- `response.headers` -- Map-like object with HTTP headers. - -Methods to get response body: -- **`response.json()`** -- parse the response as JSON object, -- **`response.text()`** -- return the response as text, -- **`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.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), - -Fetch options so far: -- `method` -- HTTP-method, -- `headers` -- an object with request headers (not any header is allowed), -- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. - -In the next chapters we'll see more options and use cases of `fetch`. diff --git a/5-network/01-fetch/post.view/logo-fetch.svg b/5-network/01-fetch/post.view/logo-fetch.svg deleted file mode 100644 index 5a58b209b5..0000000000 --- a/5-network/01-fetch/post.view/logo-fetch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 3f446376f1e3e0040fb1c5b80d111d0ac0d6b23c Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 11:12:31 +0800 Subject: [PATCH 06/11] Move 01-fetch-basics/article.md to 01-fetch/article.md --- 5-network/01-fetch/article.md | 267 +++++++++++++++++++++------------- 1 file changed, 163 insertions(+), 104 deletions(-) diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 194ad9f651..e15dc1db50 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,84 +1,72 @@ -# Fetch +# Fetch:基础 -JavaScript can send network requests to the server and load new information whenever is needed. +`fetch()` 方法是一种更为现代的发送 HTTP 请求的方式。 -For example, we can: +它从几年前被提出,一直发展至今,并且还在不断改进,现在它已经得到了很多浏览器的支持。 -- Submit an order, -- Load user information, -- Receive latest updates from the server, -- ...etc. - -...And all of that without reloading the page! - -There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. - -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 basic syntax is: +基本语法: ```js let promise = fetch(url, [options]) ``` -- **`url`** -- the URL to access. -- **`options`** -- optional parameters: method, headers etc. +- **`url`** —— 要访问的 URL。 +- **`options`** —— 可选参数:method,headers 等。 + +浏览器立即发送请求,并返回一个 `promise`。 -The browser starts the request right away and returns a `promise`. +获取响应通常需要经过两个阶段。 -Getting a response is usually a two-stage process. +**一旦服务器提供了响应头,`promise` 就会使用内置的 [Response](https://fetch.spec.whatwg.org/#response-class) 类的对象来解析。** -**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. +因此,我们可以通过检测 HTTP 状态来确定请求是否成功,或者当响应体还没有返回时,通过检查响应头来确定状态。 -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. +如果 `fetch` 无法建立一个 HTTP 请求,例如网络问题,亦或是请求的网址不存在,那么 promise 就返回 reject。HTTP 错误,即使是 404 或者 500,也被视为正常的过程。 -We can see them in response properties: +我们可以在 response 属性里看到它们: -- **`ok`** -- boolean, `true` if the HTTP status code is 200-299. -- **`status`** -- HTTP status code. +- **`ok`** —— 布尔值,如果 HTTP 状态码在 200-299 之间,返回 `true`。 +- **`status`** —— HTTP 状态码。 -For example: +例如: ```js let response = await fetch(url); -if (response.ok) { // if HTTP-status is 200-299 - // get the response body (see below) +if (response.ok) { // 如果 HTTP 状态码在 200-299 之间 + // 获取响应体(如下所示) let json = await response.json(); } else { alert("HTTP-Error: " + response.status); } ``` -**Second, to get the response body, we need to use an additional method call.** +为了获取响应体,我们需要调用其他方法。 -`Response` provides multiple promise-based methods to access the body in various formats: +`Response` 提供了多种基于 promise 的方法来获取不同格式的响应正文: -- **`response.json()`** -- parse the response as JSON object, -- **`response.text()`** -- return the response as text, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), -- **`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), -- 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. +- **`response.json()`** —— 将 response 解析为 JSON 对象, +- **`response.text()`** —— 以文本形式返回 response, +- **`response.formData()`** —— 以 FormData 对象(form/multipart encoding)形式返回 response, +- **`response.blob()`** —— 以 [Blob](info:blob) (具有类型的二进制数据)形式返回 response, +- **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays) (纯二进制数据)形式返回 response, +- 另外,`response.body` 是 [ReadableStream](https://streams.spec.whatwg.org/#rs-class) 对象,它允许逐块读取正文,我们稍后会用一个例子解释它。 -For instance, let's get a JSON-object with latest commits from GitHub: +例如,这里我们从 GitHub 获取最新提交的 JSON 对象: ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); *!* -let commits = await response.json(); // read response body and parse as JSON +let commits = await response.json(); // 获取 response body 并解析为 JSON */!* alert(commits[0].author.login); ``` -Or, the same without `await`, using pure promises syntax: +或者,我们也可以使用纯 promises 语法来实现同样的操作: ```js run fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') @@ -86,67 +74,63 @@ fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi .then(commits => alert(commits[0].author.login)); ``` -To get the text, `await response.text()` instead of `.json()`: -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -let text = await response.text(); // read response body as text - -alert(text.slice(0, 80) + '...'); +要获取文字可以使用下面方法: +```js +let text = await response.text(); ``` -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): +对于二进制的示例,让我们来获取并显示一个图片(参见 [Blob](info:blob) 章节,获取更多关于 blob 操作的详细信息): ```js async run let response = await fetch('/article/fetch/logo-fetch.svg'); *!* -let blob = await response.blob(); // download as Blob object +let blob = await response.blob(); // 以 Blob 对象下载 */!* -// create for it +// 创建 元素 let img = document.createElement('img'); img.style = 'position:fixed;top:10px;left:10px;width:100px'; document.body.append(img); -// show it +// 显示图片 img.src = URL.createObjectURL(blob); -setTimeout(() => { // hide after three seconds +setTimeout(() => { // 两秒后隐藏图片 img.remove(); URL.revokeObjectURL(img.src); -}, 3000); +}, 2000); ``` ````warn -We can choose only one body-parsing method. +我们只能选择其中一种解析响应体的方式。 -If we got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed. +如果我们以 `response.text()` 方法来获取 response,那么如果我们再用 `response.json()` 方法的话,那么这个方法是不会生效的,因为正文内容已经被处理过了。 ```js -let text = await response.text(); // response body consumed -let parsed = await response.json(); // fails (already consumed) +let text = await response.text(); // 响应体被处理 +let parsed = await response.json(); // 错误(已被处理) ```` ## Headers -There's a Map-like headers object in `response.headers`. +`response.headers` 中有一个类似于 Map 的 headers 对象。 -We can get individual headers or iterate over them: +我们可以获取单个的 headers 或者迭代它们: ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); -// get one header +// 获取其中一个 header alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 -// iterate over all headers +// 迭代所有 headers for (let [key, value] of response.headers) { alert(`${key} = ${value}`); } ``` -To set a header, we can use the `headers` option, like this: +我们可以使用 `headers` 选项来设置 header,就像这样: ```js let response = fetch(protectedUrl, { @@ -156,7 +140,7 @@ let response = fetch(protectedUrl, { }); ``` -...But there's a list of [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name) that we can't set: +但是有一些 headers 我们无法去设置它(详细列表参见 [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name)): - `Accept-Charset`, `Accept-Encoding` - `Access-Control-Request-Headers` @@ -179,20 +163,24 @@ let response = fetch(protectedUrl, { - `Proxy-*` - `Sec-*` -These headers ensure proper and safe HTTP, so they are controlled exclusively by the browser. +这些 headers 保证了 HTTP 的正确性和安全性,所以它们仅由浏览器控制。 -## POST requests +## POST 请求 -To make a `POST` request, or a request with another method, we need to use `fetch` options: +创建一个 `POST` 请求,或者其他方法(HTTP method)的请求,我们需要使用 `fetch` 相关选项: -- **`method`** -- HTTP-method, e.g. `POST`, -- **`body`** -- one of: - - a string (e.g. JSON), - - `FormData` object, to submit the data as `form/multipart`, - - `Blob`/`BufferSource` to send binary data, - - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. +- **`method`** —— HTTP 方法(HTTP-method),例如 `POST`, +- **`body`** —— 其中之一: + - 字符串(例如 JSON), + - `FormData` 对象,以 `form/multipart` 形式发送数据, + - `Blob`/`BufferSource` 发送二进制数据, + - [URLSearchParams](info:url),以 `x-www-form-urlencoded` 形式发送数据,很少使用。 -For example, this code submits `user` object as JSON: +我们来看几个例子: + +## Submit JSON + +这段代码以 JSON 形式发送 `user` 对象: ```js run async let user = { @@ -201,7 +189,7 @@ let user = { }; *!* -let response = await fetch('/article/fetch/post/user', { +let response = await fetch('/article/fetch-basics/post/user', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8' @@ -214,13 +202,42 @@ let result = await response.json(); 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. +请注意,如果 body 是字符串,`Content-Type` 默认会设置为 `text/plain;charset=UTF-8`。所以我们使用 `headers` 选项来发送 `application/json`。 + +## 发送表单 + +我们用上面同样的方法来处理 HTML `
`。 + + +```html run + + + +
+ + +``` + +这里 [FormData](https://xhr.spec.whatwg.org/#formdata) 方法会自动编码表单,`` 字段也同样被处理并以 `Content-Type: form/multipart` 来发送它。 -## Sending an image +## 发送图片 -We can also submit binary data directly using `Blob` or `BufferSource`. +我们同样可以用 `Blob` 或者 `BufferSource` 来发送二进制数据。 -For example, here's a `` where we can draw by moving a mouse. A click on the "submit" button sends the image to server: +例如,这里有个我们可以通过移动鼠标来绘制图像的 `` 元素。“submit” 按钮可以用来向服务器发送绘制的图片: ```html run autorun height="90" @@ -237,7 +254,7 @@ For example, here's a `` where we can draw by moving a mouse. A click on async function submit() { let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')); - let response = await fetch('/article/fetch/post/image', { + let response = await fetch('/article/fetch-basics/post/image', { method: 'POST', body: blob }); @@ -249,14 +266,14 @@ For example, here's a `` where we can draw by moving a mouse. A click on ``` -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`). +同样,我们也不需要手动设置 `Content-Type`,因为 `Blob` 对象有一个内置的类型(这里是 `image/png`,通过 `toBlob` 自动生成的)。 -The `submit()` function can be rewritten without `async/await` like this: +`submit()` 函数可以不使用 `async/await`,改写后如下: ```js function submit() { canvasElem.toBlob(function(blob) { - fetch('/article/fetch/post/image', { + fetch('/article/fetch-basics/post/image', { method: 'POST', body: blob }) @@ -266,37 +283,79 @@ function submit() { } ``` -## Summary +## 自定义包含图像的 FormData + +但实际上,通常将图像以及其他字段,例如 “name” 和其他元数据,来作为表单的一部分发送是非常方便的做法。 + +同样,相较于原始二进制数据,服务器更适宜于接收多部分编码(multipart-encoded)的表单。 + +```html run autorun height="90" + + + + + + + +``` + +现在,从服务器角度来看,图像是表单中的“文件”。 + +## 总结 -A typical fetch request consists of two `await` calls: +典型的 fetch 请求包含两个 `awaits`: ```js -let response = await fetch(url, options); // resolves with response headers -let result = await response.json(); // read body as json +let response = await fetch(url, options); // 解析 response headers +let result = await response.json(); // 以 JSON 形式读取数据 ``` -Or, promise-style: +或者以 promise 形式: ```js fetch(url, options) .then(response => response.json()) - .then(result => /* process result */) + .then(result => /* 处理结果 */) ``` -Response properties: -- `response.status` -- HTTP code of the response, -- `response.ok` -- `true` is the status is 200-299. -- `response.headers` -- Map-like object with HTTP headers. +响应属性: +- `response.status` —— response 的 HTTP 状态码, +- `response.ok` —— HTTP 状态码在 200-299 之间返回 `true`。 +- `response.headers` —— 类似于 Map 的 HTTP headers 对象。 -Methods to get response body: -- **`response.json()`** -- parse the response as JSON object, -- **`response.text()`** -- return the response as text, -- **`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.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data), +获取响应体的方法: +- **`response.json()`** —— 以 JSON 对象形式解析 response, +- **`response.text()`** —— 以 text 形式返回 response, +- **`response.formData()`** —— 以 FormData 对象形式返回 response(form/multipart encoding), +- **`response.blob()`** —— 以 [Blob](info:blob)(具有类型的二进制数据)形式返回 response, +- **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays)(纯二进制数据)返回 response。 -Fetch options so far: -- `method` -- HTTP-method, -- `headers` -- an object with request headers (not any header is allowed), -- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. +到目前为止我们了解的 fetch 选项包括: +- `method` —— HTTP 方法(HTTP-method), +- `headers` —— 具有请求头的 headers 对象(不是所有请求头都是被允许的) +- `body` —— 提交 string/FormData/BufferSource/Blob/UrlSearchParams 这些数据 -In the next chapters we'll see more options and use cases of `fetch`. +在下面章节中,我们将会看到更多选项和使用场景。 From ac5cf8e483a4d842211dc873d5ed696a69bd3ff3 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 11:30:53 +0800 Subject: [PATCH 07/11] Sycn changes --- 5-network/01-fetch/article.md | 141 ++++++++++------------------------ 1 file changed, 41 insertions(+), 100 deletions(-) mode change 100644 => 100755 5-network/01-fetch/article.md diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md old mode 100644 new mode 100755 index e15dc1db50..1b31726c82 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,9 +1,22 @@ -# Fetch:基础 +# Fetch -`fetch()` 方法是一种更为现代的发送 HTTP 请求的方式。 +JavaScript can send network requests to the server and load new information whenever is needed. -它从几年前被提出,一直发展至今,并且还在不断改进,现在它已经得到了很多浏览器的支持。 +For example, we can: + +- Submit an order, +- Load user information, +- Receive latest updates from the server, +- ...etc. + +...And all of that without reloading the page! + +There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. + +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. 基本语法: @@ -18,8 +31,7 @@ let promise = fetch(url, [options]) 获取响应通常需要经过两个阶段。 -**一旦服务器提供了响应头,`promise` 就会使用内置的 [Response](https://fetch.spec.whatwg.org/#response-class) 类的对象来解析。** - +**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.** 因此,我们可以通过检测 HTTP 状态来确定请求是否成功,或者当响应体还没有返回时,通过检查响应头来确定状态。 @@ -43,18 +55,18 @@ if (response.ok) { // 如果 HTTP 状态码在 200-299 之间 } ``` -为了获取响应体,我们需要调用其他方法。 +**Second, to get the response body, we need to use an additional method call.** `Response` 提供了多种基于 promise 的方法来获取不同格式的响应正文: - **`response.json()`** —— 将 response 解析为 JSON 对象, - **`response.text()`** —— 以文本形式返回 response, -- **`response.formData()`** —— 以 FormData 对象(form/multipart encoding)形式返回 response, +- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), - **`response.blob()`** —— 以 [Blob](info:blob) (具有类型的二进制数据)形式返回 response, - **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays) (纯二进制数据)形式返回 response, - 另外,`response.body` 是 [ReadableStream](https://streams.spec.whatwg.org/#rs-class) 对象,它允许逐块读取正文,我们稍后会用一个例子解释它。 -例如,这里我们从 GitHub 获取最新提交的 JSON 对象: +For instance, let's get a JSON-object with latest commits from GitHub: ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); @@ -66,7 +78,7 @@ let commits = await response.json(); // 获取 response body 并解析为 JSON alert(commits[0].author.login); ``` -或者,我们也可以使用纯 promises 语法来实现同样的操作: +Or, the same without `await`, using pure promises syntax: ```js run fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') @@ -74,12 +86,16 @@ fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi .then(commits => alert(commits[0].author.login)); ``` -要获取文字可以使用下面方法: -```js -let text = await response.text(); +To get the text, `await response.text()` instead of `.json()`: +```js run async +let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); + +let text = await response.text(); // read response body as text + +alert(text.slice(0, 80) + '...'); ``` -对于二进制的示例,让我们来获取并显示一个图片(参见 [Blob](info:blob) 章节,获取更多关于 blob 操作的详细信息): +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): ```js async run let response = await fetch('/article/fetch/logo-fetch.svg'); @@ -96,10 +112,10 @@ document.body.append(img); // 显示图片 img.src = URL.createObjectURL(blob); -setTimeout(() => { // 两秒后隐藏图片 +setTimeout(() => { // hide after three seconds img.remove(); URL.revokeObjectURL(img.src); -}, 2000); +}, 3000); ``` ````warn @@ -174,13 +190,9 @@ let response = fetch(protectedUrl, { - 字符串(例如 JSON), - `FormData` 对象,以 `form/multipart` 形式发送数据, - `Blob`/`BufferSource` 发送二进制数据, - - [URLSearchParams](info:url),以 `x-www-form-urlencoded` 形式发送数据,很少使用。 + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. -我们来看几个例子: - -## Submit JSON - -这段代码以 JSON 形式发送 `user` 对象: +For example, this code submits `user` object as JSON: ```js run async let user = { @@ -189,7 +201,7 @@ let user = { }; *!* -let response = await fetch('/article/fetch-basics/post/user', { +let response = await fetch('/article/fetch/post/user', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8' @@ -202,36 +214,7 @@ let result = await response.json(); alert(result.message); ``` -请注意,如果 body 是字符串,`Content-Type` 默认会设置为 `text/plain;charset=UTF-8`。所以我们使用 `headers` 选项来发送 `application/json`。 - -## 发送表单 - -我们用上面同样的方法来处理 HTML `
`。 - - -```html run - - - -
- - -``` - -这里 [FormData](https://xhr.spec.whatwg.org/#formdata) 方法会自动编码表单,`` 字段也同样被处理并以 `Content-Type: form/multipart` 来发送它。 +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. ## 发送图片 @@ -254,7 +237,7 @@ alert(result.message); async function submit() { let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')); - let response = await fetch('/article/fetch-basics/post/image', { + let response = await fetch('/article/fetch/post/image', { method: 'POST', body: blob }); @@ -273,7 +256,7 @@ alert(result.message); ```js function submit() { canvasElem.toBlob(function(blob) { - fetch('/article/fetch-basics/post/image', { + fetch('/article/fetch/post/image', { method: 'POST', body: blob }) @@ -283,51 +266,9 @@ function submit() { } ``` -## 自定义包含图像的 FormData - -但实际上,通常将图像以及其他字段,例如 “name” 和其他元数据,来作为表单的一部分发送是非常方便的做法。 - -同样,相较于原始二进制数据,服务器更适宜于接收多部分编码(multipart-encoded)的表单。 - -```html run autorun height="90" - - - - - - - -``` - -现在,从服务器角度来看,图像是表单中的“文件”。 - ## 总结 -典型的 fetch 请求包含两个 `awaits`: +A typical fetch request consists of two `await` calls: ```js let response = await fetch(url, options); // 解析 response headers @@ -349,13 +290,13 @@ fetch(url, options) 获取响应体的方法: - **`response.json()`** —— 以 JSON 对象形式解析 response, - **`response.text()`** —— 以 text 形式返回 response, -- **`response.formData()`** —— 以 FormData 对象形式返回 response(form/multipart encoding), +- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter), - **`response.blob()`** —— 以 [Blob](info:blob)(具有类型的二进制数据)形式返回 response, - **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays)(纯二进制数据)返回 response。 到目前为止我们了解的 fetch 选项包括: - `method` —— HTTP 方法(HTTP-method), - `headers` —— 具有请求头的 headers 对象(不是所有请求头都是被允许的) -- `body` —— 提交 string/FormData/BufferSource/Blob/UrlSearchParams 这些数据 +- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. -在下面章节中,我们将会看到更多选项和使用场景。 +In the next chapters we'll see more options and use cases of `fetch`. From 4569da1ddf673c3669c4776ee5872afb6120780a Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 15:00:50 +0800 Subject: [PATCH 08/11] Translate article.md --- 5-network/01-fetch/article.md | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 1b31726c82..e6367358f7 100755 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,22 +1,22 @@ # Fetch -JavaScript can send network requests to the server and load new information whenever is needed. +当需要加载新信息时,JavaScript 可以向服务器发送网络请求。 -For example, we can: +例如,我们可以: -- Submit an order, -- Load user information, -- Receive latest updates from the server, -- ...etc. +- 提交订单, +- 加载用户信息, +- 接受来自服务器的更新信息, +- ……等等。 -...And all of that without reloading the page! +……所有这些都没有重新加载页面! -There's an umbrella term "AJAX" (abbreviated Asynchronous Javascript And Xml) for that. We don't have to use XML though: the term comes from old times, that's that word is there. +它有个通用术语称为“AJAX”(Asynchronous Javascript And Xml 的首字母缩写)。我们不必使用 XML:这个术语很早就产生了,这个词一直在那里。 -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. +`fetch()` 方法是一种现代通用方法,那么我们就从它开始吧。它已经发展了几年了并在不断改进,现在它已经得到很多浏览器的支持了。 基本语法: @@ -31,7 +31,7 @@ let promise = fetch(url, [options]) 获取响应通常需要经过两个阶段。 -**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.** +**第一阶段,当服务器发送了响应头,`promise` 就使用其内建的 [Response](https://fetch.spec.whatwg.org/#response-class) 类来解析该对象。 因此,我们可以通过检测 HTTP 状态来确定请求是否成功,或者当响应体还没有返回时,通过检查响应头来确定状态。 @@ -55,18 +55,18 @@ if (response.ok) { // 如果 HTTP 状态码在 200-299 之间 } ``` -**Second, to get the response body, we need to use an additional method call.** +**第二阶段,为了获取响应体,我们需要调用其他方法。** `Response` 提供了多种基于 promise 的方法来获取不同格式的响应正文: - **`response.json()`** —— 将 response 解析为 JSON 对象, - **`response.text()`** —— 以文本形式返回 response, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, explained in the [next chapter](info:formdata)), +- **`response.formData()`** —— 以 `FormData` 对象(form/multipart 编码(encoding),我们将在[下一章](info:formdata)中了解到更多)的形式返回 response。 - **`response.blob()`** —— 以 [Blob](info:blob) (具有类型的二进制数据)形式返回 response, - **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays) (纯二进制数据)形式返回 response, - 另外,`response.body` 是 [ReadableStream](https://streams.spec.whatwg.org/#rs-class) 对象,它允许逐块读取正文,我们稍后会用一个例子解释它。 -For instance, let's get a JSON-object with latest commits from GitHub: +例如,我们来获取 GitHub 上最新 commits 的 JSON 对象: ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); @@ -78,7 +78,7 @@ let commits = await response.json(); // 获取 response body 并解析为 JSON alert(commits[0].author.login); ``` -Or, the same without `await`, using pure promises syntax: +也可以使用纯 promise 语法,不使用 `await`: ```js run fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') @@ -86,16 +86,16 @@ fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi .then(commits => alert(commits[0].author.login)); ``` -To get the text, `await response.text()` instead of `.json()`: +要获取文本,可以使用 `await response.text()` 代替 `.json()`: ```js run async let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); -let text = await response.text(); // read response body as text +let text = await response.text(); // 以 text 形式读取响应体 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): +我们以 fetch 并显示一张图像为例来了解一下读取二进制文件的情况(参见 [Blob](info:blob) 章节以了解更多关于 blob 的操作): ```js async run let response = await fetch('/article/fetch/logo-fetch.svg'); @@ -112,7 +112,7 @@ document.body.append(img); // 显示图片 img.src = URL.createObjectURL(blob); -setTimeout(() => { // hide after three seconds +setTimeout(() => { // 3 秒后隐藏 img.remove(); URL.revokeObjectURL(img.src); }, 3000); @@ -190,9 +190,9 @@ let response = fetch(protectedUrl, { - 字符串(例如 JSON), - `FormData` 对象,以 `form/multipart` 形式发送数据, - `Blob`/`BufferSource` 发送二进制数据, - - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. + - [URLSearchParams](info:url),以 `x-www-form-urlencoded` 编码形式发送数据,很少使用。 -For example, this code submits `user` object as JSON: +例如,下面这段代码以 JSON 形式发送 `user` 对象: ```js run async let user = { @@ -214,7 +214,7 @@ let result = await response.json(); 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. +请注意,如果 body 是字符串,`Content-Type` 默认会设置为 `text/plain;charset=UTF-8`。所以我们使用 `headers` 值为 `application/json` 来代替默认值,这是 JSON 编码的数据的正确格式。 ## 发送图片 @@ -268,7 +268,7 @@ function submit() { ## 总结 -A typical fetch request consists of two `await` calls: +典型的 fetch 请求包含两个 `await`: ```js let response = await fetch(url, options); // 解析 response headers @@ -290,13 +290,13 @@ fetch(url, options) 获取响应体的方法: - **`response.json()`** —— 以 JSON 对象形式解析 response, - **`response.text()`** —— 以 text 形式返回 response, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter), +- **`response.formData()`** —— 以 `FormData` 对象(form/multipart 编码,参见下一章)形式返回 response, - **`response.blob()`** —— 以 [Blob](info:blob)(具有类型的二进制数据)形式返回 response, - **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays)(纯二进制数据)返回 response。 到目前为止我们了解的 fetch 选项包括: - `method` —— HTTP 方法(HTTP-method), - `headers` —— 具有请求头的 headers 对象(不是所有请求头都是被允许的) -- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send. +- `body` —— 以 `string`,`FormData`,`BufferSource`,`Blob` 或者 `UrlSearchParams` 对象发送数据。 -In the next chapters we'll see more options and use cases of `fetch`. +在下一章中,我们将会看到更多关于 `fetch` 的选项以及使用场景。 From 4727ee1c9ad5c230b7cb88d3261734f9257f6396 Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Sat, 20 Jul 2019 15:01:57 +0800 Subject: [PATCH 09/11] Remove old files --- 5-network/01-fetch-basics/article.md | 361 ------------------ 5-network/01-fetch-basics/logo-fetch.svg | 4 - 5-network/01-fetch-basics/post.view/server.js | 57 --- 3 files changed, 422 deletions(-) delete mode 100644 5-network/01-fetch-basics/article.md delete mode 100644 5-network/01-fetch-basics/logo-fetch.svg delete mode 100644 5-network/01-fetch-basics/post.view/server.js diff --git a/5-network/01-fetch-basics/article.md b/5-network/01-fetch-basics/article.md deleted file mode 100644 index e15dc1db50..0000000000 --- a/5-network/01-fetch-basics/article.md +++ /dev/null @@ -1,361 +0,0 @@ - -# Fetch:基础 - -`fetch()` 方法是一种更为现代的发送 HTTP 请求的方式。 - -它从几年前被提出,一直发展至今,并且还在不断改进,现在它已经得到了很多浏览器的支持。 - -基本语法: - -```js -let promise = fetch(url, [options]) -``` - -- **`url`** —— 要访问的 URL。 -- **`options`** —— 可选参数:method,headers 等。 - -浏览器立即发送请求,并返回一个 `promise`。 - -获取响应通常需要经过两个阶段。 - -**一旦服务器提供了响应头,`promise` 就会使用内置的 [Response](https://fetch.spec.whatwg.org/#response-class) 类的对象来解析。** - - -因此,我们可以通过检测 HTTP 状态来确定请求是否成功,或者当响应体还没有返回时,通过检查响应头来确定状态。 - -如果 `fetch` 无法建立一个 HTTP 请求,例如网络问题,亦或是请求的网址不存在,那么 promise 就返回 reject。HTTP 错误,即使是 404 或者 500,也被视为正常的过程。 - -我们可以在 response 属性里看到它们: - -- **`ok`** —— 布尔值,如果 HTTP 状态码在 200-299 之间,返回 `true`。 -- **`status`** —— HTTP 状态码。 - -例如: - -```js -let response = await fetch(url); - -if (response.ok) { // 如果 HTTP 状态码在 200-299 之间 - // 获取响应体(如下所示) - let json = await response.json(); -} else { - alert("HTTP-Error: " + response.status); -} -``` - -为了获取响应体,我们需要调用其他方法。 - -`Response` 提供了多种基于 promise 的方法来获取不同格式的响应正文: - -- **`response.json()`** —— 将 response 解析为 JSON 对象, -- **`response.text()`** —— 以文本形式返回 response, -- **`response.formData()`** —— 以 FormData 对象(form/multipart encoding)形式返回 response, -- **`response.blob()`** —— 以 [Blob](info:blob) (具有类型的二进制数据)形式返回 response, -- **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays) (纯二进制数据)形式返回 response, -- 另外,`response.body` 是 [ReadableStream](https://streams.spec.whatwg.org/#rs-class) 对象,它允许逐块读取正文,我们稍后会用一个例子解释它。 - -例如,这里我们从 GitHub 获取最新提交的 JSON 对象: - -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -*!* -let commits = await response.json(); // 获取 response body 并解析为 JSON -*/!* - -alert(commits[0].author.login); -``` - -或者,我们也可以使用纯 promises 语法来实现同样的操作: - -```js run -fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') - .then(response => response.json()) - .then(commits => alert(commits[0].author.login)); -``` - -要获取文字可以使用下面方法: -```js -let text = await response.text(); -``` - -对于二进制的示例,让我们来获取并显示一个图片(参见 [Blob](info:blob) 章节,获取更多关于 blob 操作的详细信息): - -```js async run -let response = await fetch('/article/fetch/logo-fetch.svg'); - -*!* -let blob = await response.blob(); // 以 Blob 对象下载 -*/!* - -// 创建 元素 -let img = document.createElement('img'); -img.style = 'position:fixed;top:10px;left:10px;width:100px'; -document.body.append(img); - -// 显示图片 -img.src = URL.createObjectURL(blob); - -setTimeout(() => { // 两秒后隐藏图片 - img.remove(); - URL.revokeObjectURL(img.src); -}, 2000); -``` - -````warn -我们只能选择其中一种解析响应体的方式。 - -如果我们以 `response.text()` 方法来获取 response,那么如果我们再用 `response.json()` 方法的话,那么这个方法是不会生效的,因为正文内容已经被处理过了。 - -```js -let text = await response.text(); // 响应体被处理 -let parsed = await response.json(); // 错误(已被处理) -```` - -## Headers - -`response.headers` 中有一个类似于 Map 的 headers 对象。 - -我们可以获取单个的 headers 或者迭代它们: - -```js run async -let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); - -// 获取其中一个 header -alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 - -// 迭代所有 headers -for (let [key, value] of response.headers) { - alert(`${key} = ${value}`); -} -``` - -我们可以使用 `headers` 选项来设置 header,就像这样: - -```js -let response = fetch(protectedUrl, { - headers: { - Authentication: 'abcdef' - } -}); -``` - -但是有一些 headers 我们无法去设置它(详细列表参见 [forbidden HTTP headers](https://fetch.spec.whatwg.org/#forbidden-header-name)): - -- `Accept-Charset`, `Accept-Encoding` -- `Access-Control-Request-Headers` -- `Access-Control-Request-Method` -- `Connection` -- `Content-Length` -- `Cookie`, `Cookie2` -- `Date` -- `DNT` -- `Expect` -- `Host` -- `Keep-Alive` -- `Origin` -- `Referer` -- `TE` -- `Trailer` -- `Transfer-Encoding` -- `Upgrade` -- `Via` -- `Proxy-*` -- `Sec-*` - -这些 headers 保证了 HTTP 的正确性和安全性,所以它们仅由浏览器控制。 - -## POST 请求 - -创建一个 `POST` 请求,或者其他方法(HTTP method)的请求,我们需要使用 `fetch` 相关选项: - -- **`method`** —— HTTP 方法(HTTP-method),例如 `POST`, -- **`body`** —— 其中之一: - - 字符串(例如 JSON), - - `FormData` 对象,以 `form/multipart` 形式发送数据, - - `Blob`/`BufferSource` 发送二进制数据, - - [URLSearchParams](info:url),以 `x-www-form-urlencoded` 形式发送数据,很少使用。 - -我们来看几个例子: - -## Submit JSON - -这段代码以 JSON 形式发送 `user` 对象: - -```js run async -let user = { - name: 'John', - surname: 'Smith' -}; - -*!* -let response = await fetch('/article/fetch-basics/post/user', { - method: 'POST', - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: JSON.stringify(user) -}); -*/!* - -let result = await response.json(); -alert(result.message); -``` - -请注意,如果 body 是字符串,`Content-Type` 默认会设置为 `text/plain;charset=UTF-8`。所以我们使用 `headers` 选项来发送 `application/json`。 - -## 发送表单 - -我们用上面同样的方法来处理 HTML `
`。 - - -```html run - - - -
- - -``` - -这里 [FormData](https://xhr.spec.whatwg.org/#formdata) 方法会自动编码表单,`` 字段也同样被处理并以 `Content-Type: form/multipart` 来发送它。 - -## 发送图片 - -我们同样可以用 `Blob` 或者 `BufferSource` 来发送二进制数据。 - -例如,这里有个我们可以通过移动鼠标来绘制图像的 `` 元素。“submit” 按钮可以用来向服务器发送绘制的图片: - -```html run autorun height="90" - - - - - - - -``` - -同样,我们也不需要手动设置 `Content-Type`,因为 `Blob` 对象有一个内置的类型(这里是 `image/png`,通过 `toBlob` 自动生成的)。 - -`submit()` 函数可以不使用 `async/await`,改写后如下: - -```js -function submit() { - canvasElem.toBlob(function(blob) { - fetch('/article/fetch-basics/post/image', { - method: 'POST', - body: blob - }) - .then(response => response.json()) - .then(result => alert(JSON.stringify(result, null, 2))) - }, 'image/png'); -} -``` - -## 自定义包含图像的 FormData - -但实际上,通常将图像以及其他字段,例如 “name” 和其他元数据,来作为表单的一部分发送是非常方便的做法。 - -同样,相较于原始二进制数据,服务器更适宜于接收多部分编码(multipart-encoded)的表单。 - -```html run autorun height="90" - - - - - - - -``` - -现在,从服务器角度来看,图像是表单中的“文件”。 - -## 总结 - -典型的 fetch 请求包含两个 `awaits`: - -```js -let response = await fetch(url, options); // 解析 response headers -let result = await response.json(); // 以 JSON 形式读取数据 -``` - -或者以 promise 形式: -```js -fetch(url, options) - .then(response => response.json()) - .then(result => /* 处理结果 */) -``` - -响应属性: -- `response.status` —— response 的 HTTP 状态码, -- `response.ok` —— HTTP 状态码在 200-299 之间返回 `true`。 -- `response.headers` —— 类似于 Map 的 HTTP headers 对象。 - -获取响应体的方法: -- **`response.json()`** —— 以 JSON 对象形式解析 response, -- **`response.text()`** —— 以 text 形式返回 response, -- **`response.formData()`** —— 以 FormData 对象形式返回 response(form/multipart encoding), -- **`response.blob()`** —— 以 [Blob](info:blob)(具有类型的二进制数据)形式返回 response, -- **`response.arrayBuffer()`** —— 以 [ArrayBuffer](info:arraybuffer-binary-arrays)(纯二进制数据)返回 response。 - -到目前为止我们了解的 fetch 选项包括: -- `method` —— HTTP 方法(HTTP-method), -- `headers` —— 具有请求头的 headers 对象(不是所有请求头都是被允许的) -- `body` —— 提交 string/FormData/BufferSource/Blob/UrlSearchParams 这些数据 - -在下面章节中,我们将会看到更多选项和使用场景。 diff --git a/5-network/01-fetch-basics/logo-fetch.svg b/5-network/01-fetch-basics/logo-fetch.svg deleted file mode 100644 index 5a58b209b5..0000000000 --- a/5-network/01-fetch-basics/logo-fetch.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/5-network/01-fetch-basics/post.view/server.js b/5-network/01-fetch-basics/post.view/server.js deleted file mode 100644 index 18610676a0..0000000000 --- a/5-network/01-fetch-basics/post.view/server.js +++ /dev/null @@ -1,57 +0,0 @@ -const Koa = require('koa'); -const app = new Koa(); -const bodyParser = require('koa-bodyparser'); -const getRawBody = require('raw-body') -const busboy = require('async-busboy'); -const Router = require('koa-router'); - -let router = new Router(); - -router.post('/user', async (ctx) => { - ctx.body = { - message: "User saved." - }; -}); - -router.post('/image', async (ctx) => { - let body = await getRawBody(ctx.req, { - limit: '1mb' - }); - ctx.body = { - message: `Image saved, size:${body.length}.` - }; -}); - -router.post('/image-form', async (ctx) => { - - let files = []; - const { fields } = await busboy(ctx.req, { - onFile(fieldname, file, filename, encoding, mimetype) { - // read all file stream to continue - getRawBody(file, { limit: '1mb'}).then(body => { - files.push({ - fieldname, - filename, - length: body.length - }); - }) - - } - }); - - ctx.body = { - message: `Image saved, name: ${fields.name}, size:${files[0].length}.` - }; -}); - -app - .use(bodyParser()) - .use(router.routes()) - .use(router.allowedMethods()); - - -if (!module.parent) { - http.createServer(app.callback()).listen(8080); -} else { - exports.accept = app.callback(); -} From 9d4520badd61237ecdd1fe0362a62afda4bf0c4d Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Tue, 23 Jul 2019 21:01:27 +0800 Subject: [PATCH 10/11] Fix typo and permission --- 5-network/01-fetch/01-fetch-users/solution.md | 4 ++-- 5-network/01-fetch/article.md | 0 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100755 => 100644 5-network/01-fetch/article.md diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index 16878ae7ff..dac8007acc 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -2,9 +2,9 @@ 要获取一个用户,我们需要: 1. `fetch('https://api.github.com/users/USERNAME')`. -2. 如果相应状态码是 `200` 就调用 `.json()` 来读取 JS 对象。 +2. 如果响应状态码是 `200` 就调用 `.json()` 来读取 JS 对象。 -如果 `fetch` 失败,或者相应状态码不是 200,我们只要返回 `null` 到最终结果数组中就行了。 +如果 `fetch` 失败,或者响应状态码不是 200,我们只要返回 `null` 到最终结果数组中就行了。 下面是参考代码: diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md old mode 100755 new mode 100644 From 9b33e07f9f6b84c55ca95df518e75277348776be Mon Sep 17 00:00:00 2001 From: LycheeEng Date: Wed, 24 Jul 2019 11:20:21 +0800 Subject: [PATCH 11/11] Update article with review suggestion --- 5-network/01-fetch/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index e6367358f7..54df5e311e 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -7,7 +7,7 @@ - 提交订单, - 加载用户信息, -- 接受来自服务器的更新信息, +- 接受来自服务器的最新更新, - ……等等。 ……所有这些都没有重新加载页面!