You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 6-async/01-callbacks/article.md
+18-14Lines changed: 18 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,10 @@
1
1
2
2
3
-
# Introduction: sync vs async, callbacks
3
+
# Introduction: callbacks
4
4
5
5
Many actions in Javascript are *asynchronous*.
6
6
7
-
For instance, take a look at the function `loadScript(src)` that loads a script:
7
+
For instance, take a look at the function `loadScript(src)`:
8
8
9
9
```js
10
10
functionloadScript(src) {
@@ -25,14 +25,14 @@ loadScript('/my/script.js');
25
25
26
26
The function is called "asynchronous", because the action (script loading) finishes not now, but later.
27
27
28
-
The call initiates the script loading, then the execution continues normally.
28
+
The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too.
29
29
30
30
```js
31
31
loadScript('/my/script.js');
32
32
// the code below doesn't wait for the script loading to finish
33
33
```
34
34
35
-
Now let's say we want to use the new script when loads. It probably declares new functions, so we'd like to run them.
35
+
Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them.
36
36
37
37
...But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
38
38
@@ -44,12 +44,12 @@ newFunction(); // no such function!
44
44
*/!*
45
45
```
46
46
47
-
Naturally, the browser probably didn't have the time to load the script. As of now, the`loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when happens, to use new functions and variables from that script.
47
+
Naturally, the browser probably didn't have time to load the script. As of now, `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when happens, to use new functions and variables from that script.
48
48
49
49
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
75
75
76
-
Here's a runnable example with the real script:
76
+
Here's a runnable example with a real script:
77
77
78
78
```js run
79
79
functionloadScript(src, callback) {
@@ -93,7 +93,7 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', s
93
93
94
94
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
95
95
96
-
Here we did so in `loadScript`, but of course it's a general approach.
96
+
Here we did it in `loadScript`, but of course it's a general approach.
So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon.
139
+
138
140
## Handling errors
139
141
140
-
In examples above we didn't consider errors. What if a script loading fails with an error? Our callback should be able to react on that.
142
+
In examples above we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
141
143
142
144
Here's an improved version of `loadScript` that tracks loading errors:
143
145
@@ -172,7 +174,7 @@ Once again, the recipe that we used for `loadScript` is actually quite common. I
172
174
173
175
The convention is:
174
176
1. The first argument of `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
175
-
2. The second argument and successive ones if needed are for the successful result. Then `callback(null, result1, result2…)` is called.
177
+
2. The second argument (and the next ones if needed) are for the successful result. Then `callback(null, result1, result2…)` is called.
176
178
177
179
So the single `callback` function is used both for reporting errors and passing back results.
178
180
@@ -223,7 +225,7 @@ That's sometimes called "callback hell" or "pyramid of doom".
223
225
224
226
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirales out of control.
225
227
226
-
So this way of coding appears not so good.
228
+
So this way of coding isn't very good.
227
229
228
230
We can try to alleviate the problem by making every action a standalone function, like this:
229
231
@@ -257,10 +259,12 @@ function step3(error, script) {
257
259
};
258
260
```
259
261
260
-
See? It does the same, and there's no deep nesting now, because we made every action a separate top-level function. It works, but the code looks like a torn apart spreadsheet. It's difficult to read. One needs to eye-jump between pieces while reading it. That's inconvenient, especially the reader is not familiar with the code and doesn't know where to eye-jump.
262
+
See? It does the same, and there's no deep nesting now, because we made every action a separate top-level function.
263
+
264
+
It works, but the code looks like a torn apart spreadsheet. It's difficult to read, you probably noticed that. One needs to eye-jump between pieces while reading it. That's inconvenient, especially the reader is not familiar with the code and doesn't know where to eye-jump.
261
265
262
-
Also the functions named `step*` are all of a single use, they are only created to evade the "pyramid of doom". So there's a bit of a namespace cluttering here.
266
+
Also the functions named `step*` are all of a single use, they are created only to evade the "pyramid of doom". No one is going to reuse them outside of the action chain. So there's a bit of a namespace cluttering here.
263
267
264
-
We'd like to have a better way of coding for complex asynchronous actions.
268
+
We'd like to have a something better.
265
269
266
270
Luckily, there are other ways to evade such pyramids. One of the best ways is to use "promises", described in the next chapter.
Copy file name to clipboardExpand all lines: 6-async/02-promise-basics/article.md
+30-23Lines changed: 30 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,29 +8,27 @@ Everyone is happy: you, because the people don't crowd you any more, and fans, b
8
8
9
9
That was a real-life analogy for things we often have in programming:
10
10
11
-
1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's the "singer".
12
-
2. A "consuming code" wants the result when it's ready. Many functions may need that result. So that's the "fans".
13
-
3. A *promise* is a special JavaScript object that links them together. That's the "list". The producing code makes it and gives to everyone, so that they can subscribe for the result.
11
+
1. A "producing code" that does something and needs time. For instance, it loads a remote script. That's a "singer".
12
+
2. A "consuming code" wants the result when it's ready. Many functions may need that result. These are "fans".
13
+
3. A *promise* is a special JavaScript object that links them together. That's a "list". The producing code creates it and gives to everyone, so that they can subscribe for the result.
14
14
15
15
The analogy isn't very accurate, because JavaScript promises are more complex than a simple list: they have additional features and limitations. But still they are alike.
16
16
17
17
The constructor syntax for a promise object is:
18
18
19
19
```js
20
20
let promise =newPromise(function(resolve, reject) {
21
-
// producing code
21
+
//executor (the producing code, "singer")
22
22
});
23
23
```
24
24
25
25
The function passed to `new Promise` is called *executor*. When the promise is created, it's called automatically. It contains the producing code, that should eventually finish with a result. In terms of the analogy above, the executor is a "singer".
26
26
27
-
The `promise` object has internal properties:
27
+
The resulting `promise` object has internal properties:
28
28
29
29
-`state` -- initially is "pending", then changes to "fulfilled" or "rejected",
30
30
-`result` -- an arbitrary value, initially `undefined`.
31
31
32
-
Both `state` and `result` are managed by the executor.
33
-
34
32
When the executor finishes the job, it should call one of:
35
33
36
34
-`resolve(value)` -- to indicate that the job finished successfully:
@@ -42,7 +40,7 @@ When the executor finishes the job, it should call one of:
42
40
43
41

44
42
45
-
Here's a simple executor, just to see it in action:
43
+
Here's a simple executor, to gather that all together:
46
44
47
45
```js run
48
46
let promise =newPromise(function(resolve, reject) {
@@ -59,13 +57,15 @@ let promise = new Promise(function(resolve, reject) {
59
57
We can see two things by running the code above:
60
58
61
59
1. The executor is called automatically and immediately (by `new Promise`).
62
-
2.Executor arguments `resolve` and `reject`are functions that come from JavaScript engine. We don't need to create them (their string representation may vary between JavaScript engines). Instead the executor should call them when ready.
60
+
2.The executor receives two arguments:`resolve` and `reject`-- these functions come from JavaScript engine. We don't need to create them. Instead the executor should call them when ready.
63
61
64
-
After one second of thinking it calls `resolve("done")` to produce the result:
62
+
After one second of thinking the executor calls `resolve("done")` to produce the result:
65
63
66
64

67
65
68
-
The next example rejects the promise with an error:
66
+
That was an example of the "successful job completion".
67
+
68
+
And now an example where the executor rejects promise with an error:
69
69
70
70
```js
71
71
let promise =newPromise(function(resolve, reject) {
@@ -76,10 +76,12 @@ let promise = new Promise(function(resolve, reject) {
76
76
77
77

78
78
79
+
To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding promise object.
80
+
79
81
The promise that is either resolved or rejected is called "settled", as opposed to a "pending" promise.
80
82
81
83
````smart header="There can be only one result or an error"
82
-
The executor should call only one `resolve` or `reject`. And the promise state change is final.
84
+
The executor should call only one `resolve` or `reject`. The promise state change is final.
83
85
84
86
All further calls of `resolve` and `reject` are ignored:
85
87
@@ -92,7 +94,7 @@ let promise = new Promise(function(resolve, reject) {
92
94
});
93
95
```
94
96
95
-
The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many "flowing" results, for instance streams and queues. They have their own advantages and disadvantages versus promises. They are not supported by JavaScript core and lack certain language features that promises provide, so we don't cover them here, to concentrate on promises.
97
+
The idea is that a job done by the executor may have only one result or an error. In programming, there exist other data structures that allow many "flowing" results, for instance streams and queues. They have their own advantages and disadvantages versus promises. They are not supported by JavaScript core and lack certain language features that promises provide, we don't cover them here to concentrate on promises.
96
98
97
99
Also we if we call `resolve/reject` with more then one argument -- only the first argument is used, the next ones are ignored.
98
100
````
@@ -119,7 +121,7 @@ Properties `state` and `result` of a promise object are internal. We can't direc
119
121
120
122
## Consumers: ".then" and ".catch"
121
123
122
-
A promise object serves as a link between the producing code (executor) and the consuming code. "Consumers" -- functions that want to receive the result/error can be registered using methods `promise.then` and `promise.catch`.
124
+
A promise object serves as a link between the producing code (executor) and the consuming functions -- those that want to receive the result/error. Consuming functions can be registered using methods `promise.then` and `promise.catch`.
123
125
124
126
125
127
The syntax of `.then` is:
@@ -191,7 +193,7 @@ promise.catch(alert); // shows "Error: Whoops!" after 1 second
191
193
*/!*
192
194
```
193
195
194
-
So, `.catch(f)` is a complete analog of `.then(null, f)`, just a shorthand.
196
+
The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand.
If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
@@ -203,11 +205,15 @@ let promise = new Promise(resolve => resolve("done!"));
203
205
promise.then(alert); // done! (shows up right now)
204
206
```
205
207
206
-
That's good for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
208
+
That's handy for jobs that may sometimes require time and sometimes finish immediately. The handler is guaranteed to run in both cases.
209
+
````
210
+
211
+
````smart header="Handlers of `.then/catch` are always asynchronous"
212
+
To be even more precise, when `.then/catch` handler should execute, it first gets into an internal queue. The JavaScript engine takes handlers from the queue and executes when the current code finishes, similar to `setTimeout(..., 0)`.
207
213
208
-
To be precise, `.then/catch` queue up and are taken from the queue asynchronously when the current code finishes, like `setTimeout(..., 0)`.
214
+
In other words, when `.then(handler)` is going to trigger, it does something like `setTimeout(handler, 0)` instead.
209
215
210
-
So here the `alert` call is "queued" and runs immediately after the code finishes:
216
+
In the example below the promise is immediately resolved, so `.then(alert)` triggers right now: the `alert`call is queued and runs immediately after the code finishes.
211
217
212
218
```js run
213
219
// an immediately resolved promise
@@ -218,14 +224,14 @@ promise.then(alert); // done! (right after the current code finishes)
218
224
alert("code finished"); // this alert shows first
219
225
```
220
226
221
-
In practice the time for the code to finish execution is usually negligible. But the code after `.then` always executes before the `.then` handler (even in the case of a pre-resolved promise), that could matter.
227
+
So the code after `.then` always executes before the handler (even in the case of a pre-resolved promise). Usually that's unimportant, in some scenarios may matter.
222
228
````
223
229
224
-
Now let's see more practical examples to see how promises can help us in writing asynchronous code.
230
+
Now let's see more practical examples how promises can help us in writing asynchronous code.
225
231
226
232
## Example: loadScript
227
233
228
-
We have the `loadScript` function for loading a script from the previous chapter.
234
+
We've got the `loadScript` function for loading a script from the previous chapter.
229
235
230
236
Here's the callback-based variant, just to remind it:
231
237
@@ -263,6 +269,7 @@ Usage:
263
269
264
270
```js run
265
271
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
272
+
266
273
promise.then(
267
274
script => alert(`${script.src} is loaded!`),
268
275
error => alert(`Error: ${error.message}`)
@@ -276,8 +283,8 @@ We can immediately see few benefits over the callback-based syntax:
276
283
```compare minus="Callbacks" plus="Promises"
277
284
- We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called.
278
285
- There can be only one callback.
279
-
+ Promises allow us to code more naturally. First we run `loadScript`, and `.then` code what we do with the result.
280
-
+ We can call `.then` as many times as we want, at any point of time later.
286
+
+ Promises allow us to code things in the natural order. First we run `loadScript`, and `.then` write what to do with the result.
287
+
+ We can call `.then` on a promise as many times as we want, at any time later.
281
288
```
282
289
283
290
So promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
0 commit comments