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: 8-async/01-callbacks/article.md
+48-33
Original file line number
Diff line number
Diff line change
@@ -1,8 +1,10 @@
1
1
2
-
# Callback hell
3
2
3
+
# Introduction: sync vs async, callbacks
4
4
5
-
Consider this function `loadScript(src)` that loads a script:
5
+
Many actions in Javascript are *asynchronous*.
6
+
7
+
For instance, take a look at the function `loadScript(src)` that loads a script:
6
8
7
9
```js
8
10
functionloadScript(src) {
@@ -12,7 +14,7 @@ function loadScript(src) {
12
14
}
13
15
```
14
16
15
-
When the script element is added to the document, the browser loads it and executes. So, the function works.
17
+
The purpose of the function is to load a new script. When it adds the `<script src="…">`to the document, the browser loads and executes it.
16
18
17
19
We can use it like this:
18
20
@@ -21,27 +23,28 @@ We can use it like this:
21
23
loadScript('/my/script.js');
22
24
```
23
25
24
-
The function is *asynchronous*: the script starts loading now, but finishes later.
25
-
26
-
"Synchonous" and "asynchronous" are general programming terms, not specific to JavaScript.
26
+
The function is called "asynchronous", because the action (script loading) finishes not now, but later.
27
27
28
-
A *synchronous* action suspends the execution until it's completed. For instance, a call to `alert` or `prompt` is synchronous: the program may not continue until it's finished.
28
+
The call initiates the script loading, then the execution continues normally.
29
29
30
30
```js
31
-
let age =prompt("How old are you", 20);
32
-
// the execution of the code below awaits for the prompt to finish
33
-
// the script hangs
31
+
loadScript('/my/script.js');
32
+
// the code below doesn't wait for the script loading to finish
34
33
```
35
34
36
-
An *asynchronous* action allows the program to continue while it's in progress. For instance, a call to `loadScript` is asynchronous. It initiates the script loading, but does not suspend the execution. Other commands may execute while the script is loading:
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.
36
+
37
+
...But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
37
38
38
39
```js
39
-
loadScript('/my/script.js');
40
-
// the execution of the code below *does not* wait for the script loading to finish,
41
-
// it just continues
40
+
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
41
+
42
+
*!*
43
+
newFunction(); // no such function!
44
+
*/!*
42
45
```
43
46
44
-
As of now, the `loadScript` function loads the script, 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 additional functions and variables from that script.
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.
45
48
46
49
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
47
50
@@ -58,7 +61,19 @@ function loadScript(src, callback) {
58
61
}
59
62
```
60
63
61
-
Now we're able to load a script and then run our code that can use new functions from it, like here:
64
+
Now if we want to call new functions from the script, we should write that in the callback:
65
+
66
+
```js
67
+
loadScript('/my/script.js', function() {
68
+
// the callback runs after the script is loaded
69
+
newFunction(); // so now it works
70
+
...
71
+
});
72
+
```
73
+
74
+
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
75
+
76
+
Here's a runnable example with the real script:
62
77
63
78
```js run
64
79
functionloadScript(src, callback) {
@@ -69,7 +84,7 @@ function loadScript(src, callback) {
alert( _ ); // function declared in the loaded script
75
90
});
@@ -78,13 +93,13 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', f
78
93
79
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.
80
95
81
-
Here it was used for`loadScript`, but of course it's a general approach.
96
+
Here we did so in`loadScript`, but of course it's a general approach.
82
97
83
98
## Callback in callback
84
99
85
-
What if we need to load two scripts sequentially: the first one, and then the second one after it?
100
+
How to load two scripts sequentially: the first one, and then the second one after it?
86
101
87
-
We can put the second `loadScript` inside the callback, like this:
102
+
The natural solution would be to put the second `loadScript` call inside the callback, like this:
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
172
+
158
173
The convention is:
159
174
1. The first argument of `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
160
175
2. The second argument and successive ones if needed are for the successful result. Then `callback(null, result1, result2…)` is called.
161
176
162
-
So the single `callback` function is used both for reporting errors and passing back results. That's called "error-first callback" style. Or we could use different functions for successful and erroneous completion.
177
+
So the single `callback` function is used both for reporting errors and passing back results.
163
178
164
179
## Pyramid of doom
165
180
@@ -200,17 +215,17 @@ In the code above:
200
215
2. We load `2.js`, then if there's no error.
201
216
3. We load `3.js`, then if there's no error -- do something else `(*)`.
202
217
203
-
As calls become more nested, the whole thing becomes increasingly more difficult to manage, especially if we add real code instead of `...`, that may include more loops, conditional statements and other usage of loaded scripts.
218
+
As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have a real code instead of `...`, that may include more loops, conditional statements and so on.
204
219
205
220
That's sometimes called "callback hell" or "pyramid of doom".
206
221
207
222

208
223
209
-
The pyramid grows to the right with every asynchronous action. Soon it spirales out of control.
224
+
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirales out of control.
210
225
211
226
So this way of coding appears not so good.
212
227
213
-
In simple cases we can evade the problem by making every action a standalone function, like this:
228
+
We can try to alleviate the problem by making every action a standalone function, like this:
214
229
215
230
```js
216
231
loadScript('1.js', step1);
@@ -242,10 +257,10 @@ function step3(error, script) {
242
257
};
243
258
```
244
259
245
-
See? It does the same, and there's no deep nesting now, because we moved every function to the top. But the code looks like a torn apart spreadsheet. We need to eye-jump between pieces while reading it. It's not very readable, especially if you are not familiar with it and don't know where to eye-jump.
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.
246
261
247
-
Also the functions `step*`have no use, they are only created to evade the "pyramid of doom".
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.
248
263
249
-
So we'd like to have a better way of coding for complex asynchronous actions.
264
+
We'd like to have a better way of coding for complex asynchronous actions.
250
265
251
-
Luckily, there are other ways to evade such pyramids. For instance, we can use "promises", described in the next chapter.
266
+
Luckily, there are other ways to evade such pyramids. One of the best ways is to use "promises", described in the next chapter.
0 commit comments