Skip to content

Commit 68d1ac1

Browse files
committed
drafts
1 parent 973f97c commit 68d1ac1

30 files changed

+454
-259
lines changed

‎1-js/11-async/07-microtask-queue/article.md

+50-1
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,62 @@ Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in th
126126

127127
So call have a promise chain that doesn't wait for anything, then things like `setTimeout` or event handlers can never get in the middle.
128128

129+
130+
## Unhandled rejection
131+
132+
Remember "unhandled rejection" event from the chapter <info:promise-error-handling>?
133+
134+
Now, with the understanding of microtasks, we can formalize it.
135+
136+
**"Unhandled rejection" is when a promise error is not handled at the end of the microtask queue.**
137+
138+
For instance, consider this code:
139+
140+
```js run
141+
let promise = Promise.reject(new Error("Promise Failed!"));
142+
143+
window.addEventListener('unhandledrejection', event => {
144+
alert(event.reason); // Promise Failed!
145+
});
146+
```
147+
148+
We create a rejected `promise` and do not handle the error. So we have the "unhandled rejection" event (printed in browser console too).
149+
150+
We wouldn't have it if we added `.catch`, like this:
151+
152+
```js run
153+
let promise = Promise.reject(new Error("Promise Failed!"));
154+
*!*
155+
promise.catch(err => alert('caught'));
156+
*/!*
157+
158+
// no error, all quiet
159+
window.addEventListener('unhandledrejection', event => alert(event.reason));
160+
```
161+
162+
Now let's say, we'll be catching the error, but after an extremely small delay:
163+
164+
```js run
165+
let promise = Promise.reject(new Error("Promise Failed!"));
166+
*!*
167+
setTimeout(() => promise.catch(err => alert('caught')), 0);
168+
*/!*
169+
170+
// Error: Promise Failed!
171+
window.addEventListener('unhandledrejection', event => alert(event.reason));
172+
```
173+
174+
Now the unhandled rejction appears again. Why? Because `unhandledrejection` triggers when the microtask queue is complete. The engine examines promises and, if any of them is in "rejected" state, then the event is generated.
175+
176+
In the example above `setTimeout` adds the `.catch`, and it triggers too, of course it does, but later, after the event has already occured.
177+
129178
## Summary
130179

131180
- Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
132181

133182
**So, `.then/catch/finally` is called after the current code is finished.**
134183

135-
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
184+
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
136185

137186
- There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term).
138187

‎6-binary/01-arraybuffer-and-views/article.md renamed to ‎6-binary/01-arraybuffer-binary-arrays/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ArrayBuffer and views
1+
# ArrayBuffer, binary arrays
22

33
Binary data appears when we work with arbitrary files (uploading, downloading, creation). Or when we want to do image/audio processing.
44

‎6-binary/02-text-decoder/article.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# TextDecoder, TextEncoder
1+
# TextDecoder and TextEncoder
22

33
What if the binary data is actually a string?
44

‎6-binary/04-file/article.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,19 @@ Reading methods `read*` do not generate events, but rather return the result, as
102102
That's only inside a Web Worker though, because delays and hang-ups in Web Workers are less important, they do not affect the page.
103103
```
104104
105+
## Summary
105106
106-
It most often used to read from files, and
107+
`File` object inherit from `Blob`.
108+
109+
In addition to `Blob` methods and properties, `File` objects also have `fileName` and `lastModified` properties, plus the internal ability to read from filesystem. We usually get `File` objects from user input, like `<input>` or drag'n'drop.
110+
111+
`FileReader` objects can read from a file or a blob, in one of three formats:
112+
- String (`readAsText`).
113+
- `ArrayBuffer` (`readAsArrayBuffer`).
114+
- Data url, base-64 encoded (`readAsDataURL`).
115+
116+
In many cases though, we don't have to read the file contents.
117+
118+
We can create a blob url with `URL.createObjectURL(file)` and assign it to `<a>` or `<img>`. This way the file can be downloaded or show up as an image, as a part of canvas etc.
119+
120+
And if we're going to send a `File` over a network, then it's also easy, as network API like `XMLHttpRequest` or `fetch` natively accepts `File` objects.

‎7-network/1-fetch-basics/article.md

+37-193
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,17 @@ let promise = fetch(url, [params])
1616

1717
The browser starts the request right away and returns a `promise`.
1818

19-
Accepting a response is usually a two-step procedure.
19+
Accepting a response is usually a two-stage process.
2020

21-
**The `promise` resolves as soon as the server responded with headers.**
21+
**The `promise` resolves with an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class as soon as the server responds with headers.**
2222

23-
So we can access the headers, we know HTTP status, whether the response is successful, but don't have the body yet.
24-
25-
We need to wait for the response body additionally, like this:
26-
27-
```js run async
28-
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits');
29-
*!*
30-
let commits = await response.json();
31-
*/!*
32-
alert(commits[0].author.login);
33-
```
34-
35-
Or, using pure promises:
36-
37-
```js run
38-
fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits')
39-
.then(response => response.json())
40-
.then(commits => alert(commits[0].author.login));
41-
```
42-
43-
A `fetch` resolves with `response` -- an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class.
23+
So we can access the headers, we know HTTP status, whether it is successful, but don't have the body yet.
4424

4525
The main response properties are:
4626
- **`ok`** -- boolean, `true` if the HTTP status code is 200-299.
4727
- **`status`** -- HTTP status code.
4828
- **`headers`** -- HTTP headers, a Map-like object.
4929

50-
## How to get headers?
51-
5230
We can iterate over headers the same way as over a `Map`:
5331

5432
```js run async
@@ -67,25 +45,41 @@ if (response.ok) {
6745
}
6846
```
6947

70-
## How to get response?
48+
To get the response body, we need to use an additional method call.
7149

72-
`Response` allows to access the body in multiple formats, using following promises:
50+
`Response` allows to access the body in multiple formats, using following promise-based methods:
7351

7452
- **`json()`** -- parse as JSON object,
7553
- **`text()`** -- as text,
7654
- **`formData()`** -- as formData (form/multipart encoding),
7755
- **`blob()`** -- as Blob (for binary data),
78-
- **`arrayBuffer()`** -- as ArrayBuffer (for binary data),
79-
- `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk.
56+
- **`arrayBuffer()`** -- as ArrayBuffer (for binary data)
57+
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk.
8058

81-
We already saw how to get the response as json.
59+
For instance, here we get the response as JSON:
8260

83-
As text:
61+
```js run async
62+
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits');
63+
*!*
64+
let commits = await response.json();
65+
*/!*
66+
alert(commits[0].author.login);
67+
```
68+
69+
Or, using pure promises:
70+
71+
```js run
72+
fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits')
73+
.then(response => response.json())
74+
.then(commits => alert(commits[0].author.login));
75+
```
76+
77+
To get text:
8478
```js
8579
let text = await response.text();
8680
```
8781

88-
For the binary example, let's download an image and show it:
82+
And for the binary example, let's fetch and show an image (see chapter [Blob](info:blob) for details about operations on blobs):
8983

9084
```js async run
9185
let response = await fetch('/article/fetch/logo-fetch.svg');
@@ -94,172 +88,22 @@ let response = await fetch('/article/fetch/logo-fetch.svg');
9488
let blob = await response.blob(); // download as Blob object
9589
*/!*
9690

97-
// create <img> with it
91+
// create <img> for it
9892
let img = document.createElement('img');
99-
img.src = URL.createObjectURL(blob);
100-
101-
// show it for 2 seconds
102-
document.body.append(img);
10393
img.style = 'position:fixed;top:10px;left:10px;width:100px';
104-
setTimeout(() => img.remove(), 2000);
105-
```
106-
107-
## Fetch API in detail
108-
109-
The second argument provides a lot of flexibility to `fetch` syntax.
110-
111-
Here's the full list of possible options with default values (alternatives commented out):
112-
113-
```js
114-
let promise = fetch(url, {
115-
method: "GET", // POST, PUT, DELETE, etc.
116-
headers: {
117-
"Content-Type": "text/plain;charset=UTF-8"
118-
},
119-
body: undefined // string, FormData, Blob, BufferSource, or URLSearchParams
120-
referrer: "about:client", // "" for no-referrer, or an url from the current origin
121-
referrerPolicy: "", // no-referrer, no-referrer-when-downgrade, same-origin...
122-
mode: "cors", // same-origin, no-cors, navigate, or websocket
123-
credentials: "same-origin", // omit, include
124-
cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached
125-
redirect: "follow", // manual, error
126-
integrity: "", // a hash, like "sha256-abcdef1234567890"
127-
keepalive: false, // true
128-
signal: undefined, // AbortController to abort request
129-
window: window // null
130-
})
131-
```
132-
133-
134-
135-
136-
137-
## How to track progress?
138-
139-
To track download progress, we need to use `response.body`.
140-
141-
It's a "readable stream" - a special object that provides access chunk-by-chunk.
142-
143-
Here's the code to do this:
144-
145-
```js
146-
const reader = response.body.getReader();
147-
148-
while(true) {
149-
// done is true for the last chunk
150-
// value is Uint8Array of bytes
151-
const chunk = await reader.read();
152-
153-
if (chunk.done) {
154-
break;
155-
}
156-
157-
console.log(`Received ${chunk.value.length} bytes`)
158-
}
159-
```
160-
161-
We do the infinite loop, while `await reader.read()` returns response chunks.
162-
163-
A chunk has two properties:
164-
- **`done`** -- true when the reading is complete.
165-
- **`value`** -- a typed array of bytes: `Uint8Array`.
166-
167-
The full code to get response and log the progress:
168-
169-
```js run async
170-
// Step 1: start the request and obtain a reader
171-
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits?per_page=100');
172-
173-
const reader = response.body.getReader();
174-
175-
// Step 2: get total length
176-
const contentLength = +response.headers.get('Content-Length');
177-
178-
// Step 3: read the data
179-
let receivedLength = 0;
180-
let chunks = [];
181-
while(true) {
182-
const {done, value} = await reader.read();
183-
184-
if (done) {
185-
break;
186-
}
187-
188-
chunks.push(value);
189-
receivedLength += value.length;
190-
191-
console.log(`Received ${receivedLength} of ${contentLength}`)
192-
}
193-
194-
// Step 4: join chunks into result
195-
let chunksAll = new Uint8Array(receivedLength); // (4.1)
196-
let position = 0;
197-
for(let chunk of chunks) {
198-
chunksAll.set(chunk, position); // (4.2)
199-
position += chunk.length;
200-
}
94+
document.body.append(img);
20195

202-
// Step 5: decode
203-
let result = new TextDecoder("utf-8").decode(chunksMerged);
204-
let commits = JSON.parse(result);
96+
// show it
97+
img.src = URL.createObjectURL(blob);
20598

206-
// We're done!
207-
alert(commits[0].author.login);
99+
setTimeout(() => { // hide after two seconds
100+
img.remove();
101+
URL.revokeObjectURL(img.src);
102+
}, 2000);
208103
```
209104

210-
Let's explain that step-by-step:
211-
212-
1. We perform `fetch` as usual, but instead of calling `response.json()`, we obtain a stream reader `response.body.getReader()`.
213-
214-
Please note, we can't use both these methods to read the same response. Either use a reader or a response method to get the result.
215-
2. Prior to reading, we can figure out the full response length by its `Content-Length` header.
216-
217-
It may be absent for cross-domain requests (as in the example) and, well, technically a server doesn't have to set it. But usually it's at place.
218-
3. Now `await reader.read()` until it's done.
219-
220-
We gather the `chunks` in the array. That's important, because after the response is consumed, we won't be able to "re-read" it using `response.json()` or another way (you can try, there'll be an error).
221-
4. At the end, we have `chunks` -- an array of `Uint8Array` byte chunks. We need to join them into a single result. Unfortunately, there's no single method that concatenates those.
222-
1. We create `new Uint8Array(receivedLength)` -- a same-type array with the combined length.
223-
2. Then use `.set(chunk, position)` method that copies each `chunk` at the given `position` (one by one) in the resulting array.
224-
5. We have the result in `chunksAll`. It's a byte array though, not a string.
225-
226-
To create a string, we need to interpret these bytes. The built-in `TextEncoder` does exactly that. Then we can `JSON.parse` it.
227-
228-
What if it were a binary file? We could make a blob of it:
229-
```js
230-
let blob = new Blob([chunksAll.buffer]);
231-
```
232-
233-
```js run async
234-
let response = await fetch('https://api.github.com/repos/iliakan/javascript-tutorial-en/commits?per_page=100');
235-
236-
const contentLength = +response.headers.get('Content-Length');
237-
238-
const reader = response.body.getReader();
239-
240-
let receivedLength = 0;
241-
let chunks = [];
242-
while(true) {
243-
const chunk = await reader.read();
244-
245-
if (chunk.done) {
246-
console.log("done!");
247-
break;
248-
}
249-
250-
chunks.push(chunk.value);
251-
252-
receivedLength += chunk.value.length;
253-
console.log(`${receivedLength}/${contentLength} received`)
254-
}
255-
256-
let chunksMerged = new Uint8Array(receivedLength);
257-
let length = 0;
258-
for(let chunk of chunks) {
259-
chunksMerged.set(chunk, length);
260-
length += chunk.length;
261-
}
105+
```warn
106+
Please note: we can use only one of these methods.
262107
263-
let result = new TextDecoder("utf-8").decode(chunksMerged);
264-
console.log(JSON.parse(result));
108+
If we get `response.text()`, then `response.json()` won't work, as the body content has already been processed.
265109
```

0 commit comments

Comments
 (0)