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