forked from max-mapper/callback-hell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
243 lines (191 loc) · 12.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Callback Hell</title>
<link rel="stylesheet" href="stylesheets/style.css">
<link rel="stylesheet" href="stylesheets/rainbow.github.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div id="container">
<div id="main">
<div id="post" class="sticky-menu">
<div class="inner clearfix">
<div class="document prose">
<div class="surface preview">
<div class="content-preview-wrapper">
<div class="content-preview">
<div class="post-content">
# Callback Hell
*A guide to writing elegant asynchronous javascript programs*
### What is "*callback hell*"?
Asynchronous javascript, or javascript that uses callbacks, is hard to get right intuitively. A lot of code ends up looking like this:
fs.readdir(source, function(err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function(filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function(err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function(width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
See all the instances of `function` and `})`? Eek! This is affectionately known as **callback hell**.
Writing better code isn't that hard! You only need to know about a few things:
## Name your functions
Here is some (messy) browser javascript that uses [browser-request](https://github.com/iriscouch/browser-request) to make an AJAX request to a server:
var form = document.querySelector('form')
form.onsubmit = function(submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, function(err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
})
}
This code has two anonymous functions. Let's give em names!
var form = document.querySelector('form')
form.onsubmit = function formSubmit(submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, function postResponse(err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
})
}
As you can see naming functions is super easy and does some nice things to your code:
- makes code easier to read
- when exceptions happen you will get stacktraces that reference actual function names instead of "anonymous"
- allows you to keep your code shallow, or not nested deeply, which brings me to my next point:
## Keep your code shallow
Building on the last example, let's go a bit further and get rid of the triple level nesting that is going on in the code:
function formSubmit(submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, postResponse)
}
function postResponse(err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
}
document.querySelector('form').onsubmit = formSubmit
Code like this is less scary to look at and is easier to edit, refactor and hack on later.
## Modularize!
This is the most important part: **Anyone is capable of creating modules** (AKA libraries). To quote [Isaac Schlueter](http://twitter.com/izs) (of the node.js project): *"Write small modules that each do one thing, and assemble them into other modules that do a bigger thing. You can't get into callback hell if you don't go there."*
Let's take out the boilerplate code from above and turn it into a module by splitting it up into a couple of files. Since I write JavaScript in both the browser and on the server, I'll show a method that works in both but is still nice and simple.
Here is a new file called `formuploader.js` that contains our two functions from before:
function formSubmit(submitEvent) {
var name = document.querySelector('input').value
request({
uri: "http://example.com/upload",
body: name,
method: "POST"
}, postResponse)
}
function postResponse(err, response, body) {
var statusMessage = document.querySelector('.status')
if (err) return statusMessage.value = err
statusMessage.value = body
}
exports.submit = formSubmit
The `exports` bit at the end is an example of the CommonJS module system, which is used by Node.js for server side javascript programming. I quite like this style of modules because it is so simple -- you only have to define what should be shared when the module gets required (that's what the `exports` thing is).
To use CommonJS modules in the browser you can use a command-line thing called [browserify](https://github.com/substack/node-browserify). I won't go into the details on how to use it here but it lets you use `require` to load modules into your programs.
Now that we have `formuploader.js` (and it is loaded in the page as a script tag) we just need to require it and use it! Here is how our application specific code looks now:
var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit
Now our application is only two lines of code and has the following benefits:
- easier for new developers to understand -- they won't get bogged down by having to read through all of the `formuploader` functions
- `formuploader` can get used in other places without duplicating code and can easily be shared on github
- the code itself is nice and simple and easy to read
There are lots of module patterns for [web browser](http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth) and [on the server](http://nodejs.org/api/modules.html). Some of them get very complicated. The ones shown here are what I consider to be the simplest to understand.
### Additional reading
- [A pattern for decoupling DOM events from @dkastner](https://gist.github.com/3392235)
*Callback Hell is a work in progress -- check back later for more content.*
Please contribute new sections or fix existing ones by [forking this project on github](http://github.com/maxogden/callback-hell)!
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="javascripts/marked.js"></script>
<script type="text/javascript">
var content = document.querySelector('.post-content')
content.setAttribute('style', "white-space: normal;")
// kinda hacky but whatever
var lines = content.innerHTML.split('\n')
var baseIndent = lines[1].split('#')[0]
lines = lines.map(function iterator(line) {
return line.replace(baseIndent, '')
})
var trimmed = lines.join('\n')
content.innerHTML = marked(trimmed)
</script>
<script src="javascripts/rainbow.js"></script>
<script src="javascripts/generic.js"></script>
<script src="javascripts/javascript.js"></script>
<script type="text/javascript">
var codeBlocks = document.querySelectorAll('code')
Array.prototype.forEach.call(codeBlocks, function iterator(block) {
block.innerHTML = unescapeEntities(block.innerHTML)
block.setAttribute('data-language', 'javascript')
})
Rainbow.color()
// markdown is dumb and always escapes &, < and > with no way to override
function unescapeEntities(text) {
function un(text) {
var d = document.createElement("div")
d.innerHTML = text
return d.innerText || d.text || d.textContent
}
// need to unescape twice
// > -> &gt; -> > -> >
return un(un(text))
}
</script>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-33352116-1");
pageTracker._trackPageview();
} catch(err) {}
</script>
</body>
</html>