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.
|
43 | function 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
|
64 | var asyncMap = require("slide").asyncMap
|
65 | function writeFiles (files, what, cb) {
|
66 | asyncMap(files, function (f, cb) {
|
67 | fs.writeFile(f, what, cb)
|
68 | }, cb)
|
69 | }
|
70 | writeFiles([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
|
84 | function (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
|
103 | chain([
|
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
|
125 | var chain = require("slide").chain
|
126 | function 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.
|