Nodejs Controlling Flow
Nodejs Controlling Flow
Variant:
Why doesnt this work? <link to severely broken code>
Thursday, October 28, 2010
Most low level *functions* take callbacks. Beyond "hello, world" it gets tricky.
Thursday, October 28, 2010
Common Mistakes
Abandoning convention and consistency. Putting all callbacks inline. Using libraries without grokking them. Trying to make async code look sync.*
*controversial In my opinion, promises are a perfectly ne way to solve this problem. But they're complicated under the hood, and making async code look sync can cause weird expectations. Also, people like to talk about a "Promise" like it's one kind of thing, when it's actually a very general pattern.
Thursday, October 28, 2010
ow control libraries
There are approximately 7 gillion ow Everyone likes their own the best. Obvious solution: Write your own. Let's do that now.
Thursday, October 28, 2010
Please dont hold questions for the end. Please do try this at home.
Thursday, October 28, 2010
Dene Conventions
Two kinds of functions:
Actors: Take action Callbacks: Get results
Callbacks
Simple responders Must always be prepared to handle errors!
(That's why it's the rst argument.)
Often inline anonymous, but not always. Can trap and call other callbacks with
Thursday, October 28, 2010
Actors
Last argument is a callback. If any error occurs, and can't be handled,
pass it to the callback and return.
Must not throw. Return value ignored. return x ==> return cb(null, x) throw er ==> return cb(er)
Thursday, October 28, 2010
Actor Example
function actor (some, args, cb) { // last argument is callback // optional args: if (!cb && typeof(args) === "function") cb = args, args = [] // do something, and then: if (failed) cb(new Error( "failed!")) else cb(null, optionalData)
Actor Example
// return true if a path is either // a symlink or a directory. function isLinkOrDir (path, cb) { fs.lstat(path, function (er, s) { if (er) return cb(er) return cb(null, s.isDirectory() || s.isSymbolicLink()) }) }
Actor Example
// return true if a path is either // a symlink or a directory. function isLinkOrDir (path, cb) { fs.lstat(path, function (er, s) { if (er) return cb(er) return cb(null, s.isDirectory() || s.isSymbolicLink()) }) }
Actor Example
// return true if a path is either // a symlink or a directory. function isLinkOrDir (path, cb) { fs.lstat(path, function (er, s) { if (er) return cb(er) return cb(null, s.isDirectory() || s.isSymbolicLink()) }) }
Actor Example
// return true if a path is either // a symlink or a directory. function isLinkOrDir (path, cb) { fs.lstat(path, function (er, s) { if (er) return cb(er) return cb(null, s.isDirectory() || s.isSymbolicLink()) }) }
Actor Composition
// return true if a path is either // a symlink or a directory, and also // ends in ".bak" function isLinkDirBak (path, cb) { return isLinkOrDir(path, function (er, ld) { return cb(er, ld && path.substr(-4) === ".bak") }) }
Actor Composition
// return true if a path is either // a symlink or a directory, and also // ends in ".bak" function isLinkDirBak (path, cb) { return isLinkOrDir(path, function (er, ld) { return cb(er, ld && path.substr(-4) === ".bak") }) }
Actor Composition
// return true if a path is either // a symlink or a directory, and also // ends in ".bak" function isLinkDirBak (path, cb) { return isLinkOrDir(path, function (er, ld) { return cb(er, ld && path.substr(-4) === ".bak") }) }
usecase: asyncMap
I have a list of 10 les, and need to read all I have a dozen URLs, and need to fetch
of them, and then continue when they're all done. them all, and then continue when they're all done. a message to all of them, and then continue when that's done.
usecase: asyncMap
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.
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cb (er, data) { if (errState) return if (er) return cb(errState = er) results.push(data) if (-- n === 0) return cb_(null, results) } list.forEach(function (l) { fn(l, cb) }) }
Thursday, October 28, 2010
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cb (er, data) { if (errState) return if (er) return cb(errState = er) results.push(data) if (-- n === 0) return cb_(null, results) } list.forEach(function (l) { fn(l, cb) }) }
Thursday, October 28, 2010
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cb (er, data) { if (errState) return if (er) return cb(errState = er) results.push(data) if (-- n === 0) return cb_(null, results) } list.forEach(function (l) { fn(l, cb) }) }
Thursday, October 28, 2010
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cb (er, data) { if (errState) return if (er) return cb(errState = er) results.push(data) if (-- n === 0) return cb_(null, results) } list.forEach(function (l) { fn(l, cb) }) }
Thursday, October 28, 2010
usecase: asyncMap
function writeFiles (files, what, cb) { asyncMap( files , function (f, cb) { fs.writeFile(f,what,cb) } , cb ) } writeFiles([my,file,list], "foo", cb)
asyncMap
note that asyncMap itself is an Actor
function, so you can asyncMap your asyncMaps, dawg. matter, but what if it does?
asyncMap - ordered
close over the array index in the generated
cb function.
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cbGen (i) { return function cb (er, data) { if (errState) return if (er) return cb(errState = er) results[i] = data if (-- n === 0) return cb_(null, results) }} list.forEach(function (l, i) { fn(l, cbGen(i)) })
Thursday, October 28, 2010
function asyncMap (list, fn, cb_) { var n = list.length , results = [] , errState = null function cbGen (i) { return function cb (er, data) { if (errState) return if (er) return cb(errState = er) results[i] = data if (-- n === 0) return cb_(null, results) }} list.forEach(function (l, i) { fn(l, cbGen(i)) })
Thursday, October 28, 2010
usecase: chain
I have to do a bunch of things, in order. Get If anything fails, do not continue.
db credentials out of a le, read the data from the db, write that data to another le.
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
usecase: chain
Still have to provide an array of functions,
which is a lot of boilerplate, and a pita if your functions take args "function (cb){blah(a,b,c,cb)}"
reducing boilerplate
convert an array of [fn, args] to an actor
that takes no arguments (except cb) our use-case.
function bindActor () { var args = Array.prototype.slice.call (arguments) // jswtf. , obj = null , fn if (typeof args[0] === "object") { obj = args.shift() fn = args.shift() if (typeof fn === "string") fn = obj[ fn ] } else fn = args.shift() return function (cb) { fn.apply(obj, args.concat(cb)) } }
Thursday, October 28, 2010
function bindActor () { var args = Array.prototype.slice.call (arguments) // jswtf. , obj = null , fn if (typeof args[0] === "object") { obj = args.shift() fn = args.shift() if (typeof fn === "string") fn = obj[ fn ] } else fn = args.shift() return function (cb) { fn.apply(obj, args.concat(cb)) } }
Thursday, October 28, 2010
function bindActor () { var args = Array.prototype.slice.call (arguments) // jswtf. , obj = null , fn if (typeof args[0] === "object") { obj = args.shift() fn = args.shift() if (typeof fn === "string") fn = obj[ fn ] } else fn = args.shift() return function (cb) { fn.apply(obj, args.concat(cb)) } }
Thursday, October 28, 2010
function bindActor () { var args = Array.prototype.slice.call (arguments) // jswtf. , obj = null , fn if (typeof args[0] === "object") { obj = args.shift() fn = args.shift() if (typeof fn === "string") fn = obj[ fn ] } else fn = args.shift() return function (cb) { fn.apply(obj, args.concat(cb)) } }
Thursday, October 28, 2010
bindActor
Some obvious areas for improvement. They wouldn't t on a slide. Left as an exercise for the reader.
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() if (Array.isArray(things[i])) things[i] = bindActor.apply (null, things[i]) things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() if (Array.isArray(things[i])) things[i] = bindActor.apply (null, things[i]) things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
chain: branching
Skip over falsey arguments chain([ doThing && [thing,a,b,c]
, isFoo && [doFoo, "foo"] , subChain && [chain, [one, two]] ], cb)
function chain (things, cb) { ;(function LOOP (i, len) { if (i >= len) return cb() if (Array.isArray(things[i])) things[i] = bindActor.apply (null, things[i]) if (!things[i]) return LOOP(i + 1, len) things[i](function (er) { if (er) return cb(er) LOOP(i + 1, len) }) })(0, things.length) }
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) }) })(0, things.length) }
Thursday, October 28, 2010
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) }) })(0, things.length) }
Thursday, October 28, 2010
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) }) })(0, things.length) }
Thursday, October 28, 2010
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) }) })(0, things.length) }
Thursday, October 28, 2010
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) }) })(0, things.length) }
Thursday, October 28, 2010
chain.first = {} ; chain.last = {} function chain (things, res, cb) { if (!cb) cb = res , res = [] ;(function LOOP (i, len) { if (i >= len) return cb(null,res) if (Array.isArray(things[i])) things[i] = bindActor.apply(null, things[i].map(function(i){ return (i===chain.first) ? res[0] : (i===chain.last) ? res[res.length - 1] : i })) if (!things[i]) return LOOP(i + 1, len) things[i](function (er, data) { res.push(er || data) if (er) return cb(er, res) LOOP(i + 1, len) Ok, this can't get any }) bigger or it won't t. })(0, things.length) }
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
var chain = require("./chain.js") , asyncMap = require("./async-map.js") function myProgram (cb) { var res = [], last = chain.last , first = chain.first chain ( [ [fs, "readdir", "the-directory"] , [readFiles, "the-directory", last] , [sum, last] , [ping, "POST", "example.com", 80 , "/foo", last] , [fs, "writeFile", "result.txt", last] , [rmFiles, "./the-directory", first] ] , res , cb ) )
Thursday, October 28, 2010
Convention Prots
Consistent API from top to bottom. Sneak in at any point to inject functionality.
(testable, reusable, etc.)