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 000000000..da448b47a
--- /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 000000000..0c62e7bb5
--- /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 000000000..95eaf876e
--- /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 000000000..b8dfb62a2
--- /dev/null
+++ b/5-network/01-fetch/01-fetch-users/solution.md
@@ -0,0 +1,40 @@
+
+To fetch a user we need: `fetch('https://api.github.com/users/USERNAME')`.
+
+If the response has status `200`, call `.json()` to read the JS object.
+
+Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray.
+
+So here's the code:
+
+```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 000000000..4605b4955
--- /dev/null
+++ b/5-network/01-fetch/01-fetch-users/task.md
@@ -0,0 +1,13 @@
+# Fetch users from GitHub
+
+Create an async function `getUsers(names)`, that gets an array of GitHub logins, fetches the users from GitHub and returns an array of GitHub users.
+
+The GitHub url with user information 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.
+2. Requests shouldn't wait for each other. So that the data arrives as soon as possible.
+3. If any request fails, or if there's no such user, the function should return `null` in the resulting array.
diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md
new file mode 100644
index 000000000..6acd8cb59
--- /dev/null
+++ b/5-network/01-fetch/article.md
@@ -0,0 +1,316 @@
+
+# Fetch
+
+JavaScript can send network requests to the server and load new information whenever it's needed.
+
+For example, we can use a network request to:
+
+- 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 network requests from JavaScript. We don't have to use XML though: the term comes from old times, that's why that word is there. You may have heard that term already.
+
+There are multiple ways to send a network request and get information from the server.
+
+The `fetch()` method is modern and versatile, so we'll start with it. It's not supported by old browsers (can be polyfilled), but very well supported among the modern ones.
+
+The basic syntax is:
+
+```js
+let promise = fetch(url, [options])
+```
+
+- **`url`** -- the URL to access.
+- **`options`** -- optional parameters: method, headers etc.
+
+Without `options`, that is a simple GET request, downloading the contents of the `url`.
+
+The browser starts the request right away and returns a promise that the calling code should use to get the result.
+
+Getting a response is usually a two-stage process.
+
+**First, the `promise`, returned by `fetch`, 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.**
+
+At this stage we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.
+
+The promise rejects if the `fetch` was unable to make HTTP-request, e.g. network problems, or there's no such site. Abnormal HTTP-statuses, such as 404 or 500 do not cause an error.
+
+We can see HTTP-status in response properties:
+
+- **`status`** -- HTTP status code, e.g. 200.
+- **`ok`** -- boolean, `true` if the HTTP status code is 200-299.
+
+For example:
+
+```js
+let response = await fetch(url);
+
+if (response.ok) { // if HTTP-status is 200-299
+ // get the response body (the method explained 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.text()`** -- read the response and return as text,
+- **`response.json()`** -- parse the response as JSON,
+- **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)),
+- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
+- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data),
+- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you 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 url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
+let response = await fetch(url);
+
+*!*
+let commits = await response.json(); // read response body and parse as JSON
+*/!*
+
+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 response 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 a logo image of ["fetch" specification](https://fetch.spec.whatwg.org) (see chapter [Blob](info:blob) for details about operations on `Blob`):
+
+```js async run
+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-reading method.
+
+If we've already got the response with `response.text()`, then `response.json()` won't work, as the body content has already been processed.
+
+```js
+let text = await response.text(); // response body consumed
+let parsed = await response.json(); // fails (already consumed)
+```
+````
+
+## Response headers
+
+The response headers are available in a Map-like headers object in `response.headers`.
+
+It's not exactly a Map, but it has similar methods to get individual headers by name 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}`);
+}
+```
+
+## Request headers
+
+To set a request header in `fetch`, we can use the `headers` option. It has an object with outgoing headers, like this:
+
+```js
+let response = fetch(protectedUrl, {
+ headers: {
+ Authentication: 'secret'
+ }
+});
+```
+
+...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`** -- the request body, one of:
+ - a string (e.g. JSON-encoded),
+ - `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.
+
+The JSON format is used most of the time.
+
+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 request `body` is a string, then `Content-Type` header is set to `text/plain;charset=UTF-8` by default.
+
+But, as we're going to send JSON, we use `headers` option to send `application/json` instead, the correct `Content-Type` for JSON-encoded data.
+
+## Sending an image
+
+We can also submit binary data with `fetch` using `Blob` or `BufferSource` objects.
+
+In this example, there's a `