Skip to content

Commit fb5f39e

Browse files
committed
async
1 parent dc1a4fb commit fb5f39e

File tree

2 files changed

+250
-12
lines changed

2 files changed

+250
-12
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,26 +201,26 @@ Here's an example of a thenable object:
201201
202202
```js run
203203
class Thenable {
204-
constructor(result, delay) {
205-
this.result = result;
204+
constructor(num) {
205+
this.num = num;
206206
}
207-
// then method has the similar signature to promises
208207
then(resolve, reject) {
209-
// resolve with double this.result after the delay
210-
setTimeout(() => resolve(this.result * 2), delay);
208+
alert(resolve); // function() { native code }
209+
// resolve with this.num*2 after the 1 secound
210+
setTimeout(() => resolve(this.num * 2), 1000); // (**)
211211
}
212-
};
212+
}
213213
214214
new Promise(resolve => resolve(1))
215215
.then(result => {
216-
return new Thenable(result, 1000); // (*)
216+
return new Thenable(result); // (*)
217217
})
218218
.then(alert); // shows 2 after 1000ms
219219
```
220220
221-
JavaScript checks the object returned by the handler in the line `(*)`: it it has a callable method named `then`, then it waits until that method is called, and the result is passed further.
221+
JavaScript checks the object returned by `.then` handler in the line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain.
222222
223-
That allows to integrate side objects with promise chains without having to inherit from `Promise`.
223+
This feature allows to integrate custom objects with promise chains without having to inherit from `Promise`.
224224
````
225225

226226

8-async/05-async-await/article.md

Lines changed: 241 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,249 @@
11
# Async/await
22

3-
Keywords `async` and `await` provide a more elegant way to write the code using promises.
3+
There's a special syntax to work with promises in a more comfort fashion, called "async/await". It's surprisingly easy to understand and use.
44

55
## Async functions
66

7-
The `async` function is like a regular one, but it wraps a returned value in a `Promise`.
7+
Let's start with the `async` keyword. It can be placed before function, like this:
88

9+
```js
10+
async function f() {
11+
return 1;
12+
}
13+
```
914

15+
The word "async" before a function means one simple thing: a function always returns a promise. If it's not so, then the value is wrapped in `Promise.resolve`.
1016

11-
Nowadays, promises are de-facto standard for asynchronous actions, when we need to
17+
For instance, the code above returns `Promise.resolve(1)`:
18+
19+
```js run
20+
async function f() {
21+
return 1;
22+
}
23+
24+
f().then(alert); // 1
25+
```
26+
27+
...We can explicitly return a promise, that would be the same:
28+
29+
```js run
30+
async function f() {
31+
return Promise.resolve(1);
32+
}
33+
34+
f().then(alert); // 1
35+
```
36+
37+
So, `async` ensures that the function returns a promise.
38+
39+
But not only that. There's another keyword `await` that works only inside `async` functions.
40+
41+
## Await
42+
43+
The syntax:
44+
45+
```js
46+
// works only inside async functions
47+
let value = await promise;
48+
```
49+
50+
The keyword `await` before a promise makes JavaScript to wait until that promise settles and return its result.
51+
52+
For instance, the code below shows "done!" after one second:
53+
54+
```js run
55+
async function f() {
56+
57+
let promise = new Promise((resolve, reject) => {
58+
setTimeout(() => resolve("done!"), 1000)
59+
});
60+
61+
*!*
62+
let result = await promise; // wait till the promise resolves
63+
*/!*
64+
65+
alert(result); // "done!"
66+
}
67+
68+
f();
69+
```
70+
71+
Let's emphasize that: `await` literally makes JavaScript to wait until the promise settles, and then continue with the result. That doesn't cost any CPU resources, because the engine can do other jobs meanwhile: execute other scripts, handle events etc.
72+
73+
It's just a more elegant syntax of getting promise result than `promise.then`.
74+
75+
````warn header="Can't use `await` in regular functions"
76+
If we try to use `await` in non-async function, that would be a syntax error:
77+
78+
```js run
79+
function f() {
80+
let promise = Promise.resolve(1); // any promise
81+
*!*
82+
let result = await promise; // Syntax error
83+
*/!*
84+
}
85+
```
86+
87+
Usually we get such error when we forget to put `async` before a function.
88+
````
89+
90+
Let's take an avatar-showing example from the chapter <info:promise-chaining> and rewrite it using `async/await`:
91+
92+
```js run
93+
async function showAvatar() {
94+
95+
let response = await fetch('/article/promise-chaining/user.json');
96+
let user = await response.json();
97+
98+
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
99+
let githubUser = await githubResponse.json();
100+
101+
let img = document.createElement('img');
102+
img.src = githubUser.avatar_url;
103+
img.className = "promise-avatar-example";
104+
document.body.append(img);
105+
106+
// wait 3 seconds
107+
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
108+
109+
img.remove();
110+
111+
return githubUser;
112+
}
113+
114+
showAvatar();
115+
```
116+
117+
Pretty clean and easy to read, right? And works the same as before.
118+
119+
Once again, please note that we can't write `await` in top-level code:
120+
121+
```js
122+
// syntax error
123+
let response = await fetch('/article/promise-chaining/user.json');
124+
let user = await response.json();
125+
```
126+
127+
...We need to wrap it into an async function.
128+
129+
````smart header="Await accepts thenables"
130+
Like `promise.then`, `await` allows to use thenable objects (those with a callable `then` method). Again, the idea is that a 3rd-party object may be promise-compatible: if it supports `.then`, that's enough.
131+
132+
For instance:
133+
```js run
134+
class Thenable {
135+
constructor(num) {
136+
this.num = num;
137+
}
138+
then(resolve, reject) {
139+
alert(resolve); // function() { native code }
140+
// resolve with this.num*2 after 1000ms
141+
setTimeout(() => resolve(this.num * 2), 1000); // (*)
142+
}
143+
};
144+
145+
async function f() {
146+
// waits for 1 second, then result becomes 2
147+
let result = await new Thenable(1);
148+
alert(result);
149+
}
150+
151+
f();
152+
```
153+
Just like with promise chains, if `await` detects an object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called `(*)`and proceeds with the result.
154+
````
155+
156+
## Error handling
157+
158+
If a promise resolves normally, then `await promise` returns the result. But in case of a rejection it throws an error, just if there were a `throw` statement at that line.
159+
160+
This code:
161+
162+
```js
163+
async function f() {
164+
await Promise.reject(new Error("Whoops!"));
165+
}
166+
```
167+
168+
...Is the same as this:
169+
170+
```js
171+
async function f() {
172+
throw new Error("Whoops!");
173+
}
174+
```
175+
176+
In real situations the promise may take time before it rejects. So `await` will wait for some time, then throw an error.
177+
178+
We can catch that error using `try..catch`, the same way as a regular `throw`:
179+
180+
```js run
181+
async function f() {
182+
183+
try {
184+
let response = await fetch('http://no-such-url');
185+
} catch(err) {
186+
*!*
187+
alert(err); // TypeError: failed to fetch
188+
*/!*
189+
}
190+
}
191+
192+
f();
193+
```
194+
195+
In case of an error, the control jumps to the `catch`, so we can wrap multiple lines:
196+
197+
```js run
198+
async function f() {
199+
200+
try {
201+
let response = await fetch('/no-user-here');
202+
let user = await response.json();
203+
} catch(err) {
204+
// catches errors both in fetch and response.json
205+
alert(err);
206+
}
207+
}
208+
209+
f();
210+
```
211+
212+
If we don't have `try..catch`, then the promise generated by the async function `f` becomes rejected, so we can catch the error on it like this:
213+
214+
```js run
215+
async function f() {
216+
let response = await fetch('http://no-such-url');
217+
}
218+
219+
*!*
220+
f().catch(alert); // TypeError: failed to fetch // (*)
221+
*/!*
222+
```
223+
224+
If we also forget to add `.catch` there, then we get an unhandled promise error. We can catch such errors using a global event handler as described in the chapter <info:promise-chaining>.
225+
226+
227+
```smart header="`async/await` and `promise.then/catch`"
228+
When we use `async/await`, we rarely need `.then`, because `await` handles the waiting for us. And we can use a regular `try..catch` instead of `.catch`, that's usually (not always) more convenient.
229+
230+
But at the top-level of the code, when we're calling the outmost `async` function, we're syntactically unable to use `await` (as we're not in an `async` function yet), so it's a normal practice to add `.then/catch` to handle the final result or falling-through errors.
231+
232+
Like in the line `(*)` of the example above.
233+
```
234+
235+
````smart header="Async/await works well with `Promise.all`"
236+
When we need to wait for multiple promises, we can wrap them in `Promise.all` and then `await`:
237+
238+
```js
239+
// wait for the array of results
240+
let results = await Promise.all([
241+
fetch(url1),
242+
fetch(url2),
243+
...
244+
]);
245+
```
246+
247+
In case of an error, it propagates as usual: from the failed promise to `Promise.all`, and then becomes an exception that we can catch using `try..catch` around the call.
248+
249+
````

0 commit comments

Comments
 (0)