Skip to content

Commit 5d1813b

Browse files
committed
up
1 parent d184bf8 commit 5d1813b

File tree

30 files changed

+626
-17107
lines changed

30 files changed

+626
-17107
lines changed

‎8-async/01-callback-hell/article.md renamed to ‎8-async/01-callbacks/article.md

+48-33
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11

2-
# Callback hell
32

3+
# Introduction: sync vs async, callbacks
44

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:
68

79
```js
810
function loadScript(src) {
@@ -12,7 +14,7 @@ function loadScript(src) {
1214
}
1315
```
1416

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.
1618

1719
We can use it like this:
1820

@@ -21,27 +23,28 @@ We can use it like this:
2123
loadScript('/my/script.js');
2224
```
2325

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.
2727

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.
2929

3030
```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
3433
```
3534

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:
3738

3839
```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+
*/!*
4245
```
4346

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.
4548

4649
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
4750

@@ -58,7 +61,19 @@ function loadScript(src, callback) {
5861
}
5962
```
6063

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:
6277

6378
```js run
6479
function loadScript(src, callback) {
@@ -69,7 +84,7 @@ function loadScript(src, callback) {
6984
}
7085

7186
*!*
72-
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', function(script) {
87+
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
7388
alert(`Cool, the ${script.src} is loaded`);
7489
alert( _ ); // function declared in the loaded script
7590
});
@@ -78,13 +93,13 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', f
7893

7994
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.
8095

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.
8297

8398
## Callback in callback
8499

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?
86101

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:
88103

89104
```js
90105
loadScript('/my/script.js', function(script) {
@@ -100,7 +115,7 @@ loadScript('/my/script.js', function(script) {
100115
});
101116
```
102117

103-
Now after the outer `loadScript` is complete, the callback initiates the inner one.
118+
After the outer `loadScript` is complete, the callback initiates the inner one.
104119

105120
...What if we want one more script?
106121

@@ -120,11 +135,9 @@ loadScript('/my/script.js', function(script) {
120135
});
121136
```
122137

123-
As we can see, a new asynchronous action means one more nesting level. So the code becomes deeper and deeper.
124-
125138
## Handling errors
126139

127-
In examples above we didn't consider errors. What if a script loading failed with an error? Our callback should be able to react on that.
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.
128141

129142
Here's an improved version of `loadScript` that tracks loading errors:
130143

@@ -144,7 +157,7 @@ function loadScript(src, callback) {
144157

145158
It calls `callback(null, script)` for successful load and `callback(error)` otherwise.
146159

147-
Usage:
160+
The usage:
148161
```js
149162
loadScript('/my/script.js', function(error, script) {
150163
if (error) {
@@ -155,11 +168,13 @@ loadScript('/my/script.js', function(error, script) {
155168
});
156169
```
157170

171+
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
172+
158173
The convention is:
159174
1. The first argument of `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
160175
2. The second argument and successive ones if needed are for the successful result. Then `callback(null, result1, result2…)` is called.
161176

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.
163178

164179
## Pyramid of doom
165180

@@ -200,17 +215,17 @@ In the code above:
200215
2. We load `2.js`, then if there's no error.
201216
3. We load `3.js`, then if there's no error -- do something else `(*)`.
202217

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.
204219

205220
That's sometimes called "callback hell" or "pyramid of doom".
206221

207222
![](callback-hell.png)
208223

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.
210225

211226
So this way of coding appears not so good.
212227

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:
214229

215230
```js
216231
loadScript('1.js', step1);
@@ -242,10 +257,10 @@ function step3(error, script) {
242257
};
243258
```
244259

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.
246261

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.
248263

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.
250265

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.
File renamed without changes.

0 commit comments

Comments
 (0)