README.md
1# Controlling Flow: callbacks are easy
2
3## What's actually hard?
4
5- Doing a bunch of things in a specific order.
6- Knowing when stuff is done.
7- Handling failures.
8- Breaking up functionality into parts (avoid nested inline callbacks)
9
10
11## Common Mistakes
12
13- Abandoning convention and consistency.
14- Putting all callbacks inline.
15- Using libraries without grokking them.
16- Trying to make async code look sync.
17
18## Define Conventions
19
20- Two kinds of functions: *actors* take action, *callbacks* get results.
21- Essentially the continuation pattern. Resulting code *looks* similar
22 to fibers, but is *much* simpler to implement.
23- Node works this way in the lowlevel APIs already, and it's very flexible.
24
25## Callbacks
26
27- Simple responders
28- Must always be prepared to handle errors, that's why it's the first argument.
29- Often inline anonymous, but not always.
30- Can trap and call other callbacks with modified data, or pass errors upwards.
31
32## Actors
33
34- Last argument is a callback.
35- If any error occurs, and can't be handled, pass it to the callback and return.
36- Must not throw. Return value ignored.
37- return x ==> return cb(null, x)
38- throw er ==> return cb(er)
39
40```javascript
41// return true if a path is either
42// a symlink or a directory.
43function isLinkOrDir (path, cb) {
44 fs.lstat(path, function (er, s) {
45 if (er) return cb(er)
46 return cb(null, s.isDirectory() || s.isSymbolicLink())
47 })
48}
49```
50
51# asyncMap
52
53## Usecases
54
55- I have a list of 10 files, and need to read all of them, and then continue when they're all done.
56- I have a dozen URLs, and need to fetch them all, and then continue when they're all done.
57- I have 4 connected users, and need to send a message to all of them, and then continue when that's done.
58- I have a list of n things, and I need to dosomething with all of them, in parallel, and get the results once they're all complete.
59
60
61## Solution
62
63```javascript
64var asyncMap = require("slide").asyncMap
65function writeFiles (files, what, cb) {
66 asyncMap(files, function (f, cb) {
67 fs.writeFile(f, what, cb)
68 }, cb)
69}
70writeFiles([my, file, list], "foo", cb)
71```
72
73# chain
74
75## Usecases
76
77- I have to do a bunch of things, in order. Get db credentials out of a file,
78 read the data from the db, write that data to another file.
79- If anything fails, do not continue.
80- I still have to provide an array of functions, which is a lot of boilerplate,
81 and a pita if your functions take args like
82
83```javascript
84function (cb) {
85 blah(a, b, c, cb)
86}
87```
88
89- Results are discarded, which is a bit lame.
90- No way to branch.
91
92## Solution
93
94- reduces boilerplate by converting an array of [fn, args] to an actor
95 that takes no arguments (except cb)
96- A bit like Function#bind, but tailored for our use-case.
97- bindActor(obj, "method", a, b, c)
98- bindActor(fn, a, b, c)
99- bindActor(obj, fn, a, b, c)
100- branching, skipping over falsey arguments
101
102```javascript
103chain([
104 doThing && [thing, a, b, c]
105, isFoo && [doFoo, "foo"]
106, subChain && [chain, [one, two]]
107], cb)
108```
109
110- tracking results: results are stored in an optional array passed as argument,
111 last result is always in results[results.length - 1].
112- treat chain.first and chain.last as placeholders for the first/last
113 result up until that point.
114
115
116## Non-trivial example
117
118- Read number files in a directory
119- Add the results together
120- Ping a web service with the result
121- Write the response to a file
122- Delete the number files
123
124```javascript
125var chain = require("slide").chain
126function myProgram (cb) {
127 var res = [], last = chain.last, first = chain.first
128 chain([
129 [fs, "readdir", "the-directory"]
130 , [readFiles, "the-directory", last]
131 , [sum, last]
132 , [ping, "POST", "example.com", 80, "/foo", last]
133 , [fs, "writeFile", "result.txt", last]
134 , [rmFiles, "./the-directory", first]
135 ], res, cb)
136}
137```
138
139# Conclusion: Convention Profits
140
141- Consistent API from top to bottom.
142- Sneak in at any point to inject functionality. Testable, reusable, ...
143- When ruby and python users whine, you can smile condescendingly.
144