Skip to content

Commit 787d58a

Browse files
committed
Publish async/await!
1 parent fb5f39e commit 787d58a

File tree

10 files changed

+253
-45
lines changed

10 files changed

+253
-45
lines changed

8-async/03-promise-chaining/article.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,9 @@ In theory, nothing should happen. In case of an error happens, the promise state
637637

638638
In practice, that means that the code is bad. Indeed, how come that there's no error handling?
639639

640-
Most JavaScript engines track such situations and generate a global error in that case. In the browser we can catch it using the event `unhandledrejection`:
640+
Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
641+
642+
In the browser we can catch it using the event `unhandledrejection`:
641643

642644
```js run
643645
*!*

8-async/04-promise-api/article.md

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,29 +71,48 @@ The syntax is:
7171
let promise = Promise.all(iterable);
7272
```
7373

74-
It takes an `iterable` object with promises, for instance an array and returns a new promise that resolves with when all of them are settled and has an array of results.
74+
It takes an `iterable` object with promises, technically it can be any iterable, but usually it's an array, and returns a new promise. The new promise resolves with when all of them are settled and has an array of their results.
7575

76-
For instance, the `Promise.all` below settles after 3 seconds, and the result is an array `[1, 2, 3]`:
76+
For instance, the `Promise.all` below settles after 3 seconds, and then its result is an array `[1, 2, 3]`:
7777

7878
```js run
7979
Promise.all([
80-
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)),
81-
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)),
82-
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000))
83-
]).then(alert); // 1,2,3 after all promises are ready
80+
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
81+
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
82+
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3
83+
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
8484
```
8585

8686
Please note that the relative order is the same. Even though the first promise takes the longest time to resolve, it is still first in the array of results.
8787

88-
A more real-life example with fetching user information for an array of github users by their names:
88+
A common trick is to map an array of job data into an array of promises, and then wrap that into `Promise.all`.
89+
90+
For instance, if we have an array of URLs, we can fetch them all like this:
8991

9092
```js run
91-
let names = ['iliakan', 'remy', 'jresig'];
93+
let urls = [
94+
'https://api.github.com/users/iliakan',
95+
'https://api.github.com/users/remy',
96+
'https://api.github.com/users/jeresig'
97+
];
9298

9399
// map every url to the promise fetch(github url)
94-
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
100+
let requests = urls.map(url => fetch(url));
95101

96102
// Promise.all waits until all jobs are resolved
103+
Promise.all(requests)
104+
.then(responses => responses.forEach(
105+
response => alert(`${response.url}: ${response.status}`)
106+
));
107+
```
108+
109+
A more real-life example with fetching user information for an array of github users by their names (or we could fetch an array of goods by their ids, the logic is same):
110+
111+
```js run
112+
let names = ['iliakan', 'remy', 'jeresig'];
113+
114+
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
115+
97116
Promise.all(requests)
98117
.then(responses => {
99118
// all responses are ready, we can show HTTP status codes
@@ -103,13 +122,13 @@ Promise.all(requests)
103122

104123
return responses;
105124
})
106-
// map array of responses into array of response.json() and wrap them info Promise.all
125+
// map array of responses into array of response.json() to read their content
107126
.then(responses => Promise.all(responses.map(r => r.json())))
108-
// all JSON answers are parsed: users is the array of them
127+
// all JSON answers are parsed: "users" is the array of them
109128
.then(users => users.forEach(user => alert(user.name)));
110129
```
111130

112-
If any of the promises is rejected, `Promise.all` also rejects with that error.
131+
If any of the promises is rejected, `Promise.all` immediately rejects with that error.
113132

114133
For instance:
115134

@@ -124,12 +143,14 @@ Promise.all([
124143
]).catch(alert); // Error: Whoops!
125144
```
126145

127-
Here the `.catch` runs after 2 seconds, when the second promise rejects, and the rejection error becomes the outcome of the whole `Promise.all`.
146+
Here the second promise rejects in two seconds. That leads to immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the whole `Promise.all`.
147+
148+
The important detail is that promises provide no way to "cancel" or "abort" their execution. So other promises continue to execute, and the eventually settle, but all their results are ignored.
128149

129-
The important detail is that promises provide no way to "cancel" or "abort" their execution. So in the example above all promises finally settle, but in case of an error all results are ignored, "thrown away".
150+
There are ways to avoid this: we can either write additional code to `clearTimeout` (or otherwise cancel) the promises in case of an error, or we can make errors show up as members in the resulting array (see the task below this chapter about it).
130151

131-
````smart header="`Promise.all` wraps non-promise arguments into `Promise.resolve`"
132-
Normally, `Promise.all(iterable)` accepts an iterable of promise objects. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
152+
````smart header="`Promise.all(iterable)` allows non-promise items in `iterable`"
153+
Normally, `Promise.all(iterable)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
133154

134155
For instance, here the results are `[1, 2, 3]`:
135156

@@ -167,7 +188,7 @@ Promise.race([
167188
]).then(alert); // 1
168189
```
169190
170-
So, the first result/error becomes the result of the whole `Promise.race`, and further results/errors are ignored.
191+
So, the first result/error becomes the result of the whole `Promise.race`. After the first settled promise "wins the race", all further results/errors are ignored.
171192
172193
## Summary
173194
@@ -177,3 +198,5 @@ There are 4 static methods of `Promise` class:
177198
2. `Promise.reject(error)` -- makes a rejected promise with the given error,
178199
3. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored.
179200
4. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
201+
202+
Of these four, `Promise.all` is the most common in practice.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
There are no tricks here. Just replace `.catch` with `try...catch` inside `demoGithubUser` and add `async/await` where needed:
3+
4+
```js run
5+
class HttpError extends Error {
6+
constructor(response) {
7+
super(`${response.status} for ${response.url}`);
8+
this.name = 'HttpError';
9+
this.response = response;
10+
}
11+
}
12+
13+
async function loadJson(url) {
14+
let response = await fetch(url);
15+
if (response.status == 200) {
16+
return response.json();
17+
} else {
18+
throw new HttpError(response);
19+
}
20+
}
21+
22+
// Ask for a user name until github returns a valid user
23+
async function demoGithubUser() {
24+
25+
let user;
26+
while(true) {
27+
let name = prompt("Enter a name?", "iliakan");
28+
29+
try {
30+
user = await loadJson(`https://api.github.com/users/${name}`);
31+
break; // no error, exit loop
32+
} catch(err) {
33+
if (err instanceof HttpError && err.response.status == 404) {
34+
// loop continues after the alert
35+
alert("No such user, please reenter.");
36+
} else {
37+
// unknown error, rethrow
38+
throw err;
39+
}
40+
}
41+
}
42+
43+
44+
alert(`Full name: ${user.name}.`);
45+
return user;
46+
}
47+
48+
demoGithubUser();
49+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
# Rewrite "rethrow" async/await
3+
4+
Rewrite the "rethrow" example from the chapter <info:promise-chaining> using `async/await` instead of `.then/catch`.
5+
6+
And get rid of recursion in favour of a loop in `demoGithubUser`: with `async/await` that becomes possible and is easier to develop later on.
7+
8+
```js run
9+
class HttpError extends Error {
10+
constructor(response) {
11+
super(`${response.status} for ${response.url}`);
12+
this.name = 'HttpError';
13+
this.response = response;
14+
}
15+
}
16+
17+
function loadJson(url) {
18+
return fetch(url)
19+
.then(response => {
20+
if (response.status == 200) {
21+
return response.json();
22+
} else {
23+
throw new HttpError(response);
24+
}
25+
})
26+
}
27+
28+
// Ask for a user name until github returns a valid user
29+
function demoGithubUser() {
30+
let name = prompt("Enter a name?", "iliakan");
31+
32+
return loadJson(`https://api.github.com/users/${name}`)
33+
.then(user => {
34+
alert(`Full name: ${user.name}.`);
35+
return user;
36+
})
37+
.catch(err => {
38+
if (err instanceof HttpError && err.response.status == 404) {
39+
alert("No such user, please reenter.");
40+
return demoGithubUser();
41+
} else {
42+
throw err;
43+
}
44+
});
45+
}
46+
47+
demoGithubUser();
48+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
The notes are below the code:
3+
4+
```js run
5+
async function loadJson(url) { // (1)
6+
let response = await fetch(url); // (2)
7+
8+
if (response.status == 200) {
9+
let json = await response.json(); // (3)
10+
return json;
11+
}
12+
13+
throw new Error(response.status);
14+
}
15+
16+
loadJson('no-such-user.json')
17+
.catch(alert); // Error: 404 (4)
18+
```
19+
20+
Notes:
21+
22+
1. The function `loadUrl` becomes `async`.
23+
2. All `.then` inside are replaced with `await`.
24+
3. We can `return response.json()` instead of awaiting for it, like this:
25+
26+
```js
27+
if (response.status == 200) {
28+
return response.json(); // (3)
29+
}
30+
```
31+
32+
Then the outer code would have to `await` for that promise to resolve. In our case it doesn't matter.
33+
4. The error thrown from `loadJson` is handled by `.catch`. We can't use `await loadJson(…)` there, because we're not in an `async` function.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
# Rewrite using async/await
3+
4+
Rewrite the one of examples from the chapter <info:promise-chaining> using `async/await` instead of `.then/catch`:
5+
6+
```js run
7+
function loadJson(url) {
8+
return fetch(url)
9+
.then(response => {
10+
if (response.status == 200) {
11+
return response.json();
12+
} else {
13+
throw new Error(response.status);
14+
}
15+
})
16+
}
17+
18+
loadJson('no-such-user.json') // (3)
19+
.catch(alert); // Error: 404
20+
```

0 commit comments

Comments
 (0)