pax_global_header00006660000000000000000000000064124047015740014516gustar00rootroot0000000000000052 comment=8345e51ee41e35825abc1a40750ea11462f57028 slide-flow-control-1.1.6/000077500000000000000000000000001240470157400152465ustar00rootroot00000000000000slide-flow-control-1.1.6/LICENSE000066400000000000000000000013541240470157400162560ustar00rootroot00000000000000The ISC License Copyright (c) Isaac Z. Schlueter Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. slide-flow-control-1.1.6/README.md000066400000000000000000000076551240470157400165420ustar00rootroot00000000000000# Controlling Flow: callbacks are easy ## What's actually hard? - Doing a bunch of things in a specific order. - Knowing when stuff is done. - Handling failures. - Breaking up functionality into parts (avoid nested inline callbacks) ## Common Mistakes - Abandoning convention and consistency. - Putting all callbacks inline. - Using libraries without grokking them. - Trying to make async code look sync. ## Define Conventions - Two kinds of functions: *actors* take action, *callbacks* get results. - Essentially the continuation pattern. Resulting code *looks* similar to fibers, but is *much* simpler to implement. - Node works this way in the lowlevel APIs already, and it's very flexible. ## Callbacks - Simple responders - Must always be prepared to handle errors, that's why it's the first argument. - Often inline anonymous, but not always. - Can trap and call other callbacks with modified data, or pass errors upwards. ## 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) ```javascript // 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()) }) } ``` # asyncMap ## Usecases - I have a list of 10 files, and need to read all of them, and then continue when they're all done. - I have a dozen URLs, and need to fetch them all, and then continue when they're all done. - I have 4 connected users, and need to send a message to all of them, and then continue when that's done. - 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. ## Solution ```javascript var asyncMap = require("slide").asyncMap function writeFiles (files, what, cb) { asyncMap(files, function (f, cb) { fs.writeFile(f, what, cb) }, cb) } writeFiles([my, file, list], "foo", cb) ``` # chain ## Usecases - I have to do a bunch of things, in order. Get db credentials out of a file, read the data from the db, write that data to another file. - If anything fails, do not continue. - I still have to provide an array of functions, which is a lot of boilerplate, and a pita if your functions take args like ```javascript function (cb) { blah(a, b, c, cb) } ``` - Results are discarded, which is a bit lame. - No way to branch. ## Solution - reduces boilerplate by converting an array of [fn, args] to an actor that takes no arguments (except cb) - A bit like Function#bind, but tailored for our use-case. - bindActor(obj, "method", a, b, c) - bindActor(fn, a, b, c) - bindActor(obj, fn, a, b, c) - branching, skipping over falsey arguments ```javascript chain([ doThing && [thing, a, b, c] , isFoo && [doFoo, "foo"] , subChain && [chain, [one, two]] ], cb) ``` - tracking results: results are stored in an optional array passed as argument, last result is always in results[results.length - 1]. - treat chain.first and chain.last as placeholders for the first/last result up until that point. ## Non-trivial example - Read number files in a directory - Add the results together - Ping a web service with the result - Write the response to a file - Delete the number files ```javascript var chain = require("slide").chain 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) } ``` # Conclusion: Convention Profits - Consistent API from top to bottom. - Sneak in at any point to inject functionality. Testable, reusable, ... - When ruby and python users whine, you can smile condescendingly. slide-flow-control-1.1.6/index.js000066400000000000000000000000461240470157400167130ustar00rootroot00000000000000module.exports=require("./lib/slide") slide-flow-control-1.1.6/lib/000077500000000000000000000000001240470157400160145ustar00rootroot00000000000000slide-flow-control-1.1.6/lib/async-map-ordered.js000066400000000000000000000033011240470157400216610ustar00rootroot00000000000000 throw new Error("TODO: Not yet implemented.") /* usage: Like asyncMap, but only can take a single cb, and guarantees the order of the results. */ module.exports = asyncMapOrdered function asyncMapOrdered (list, fn, cb_) { if (typeof cb_ !== "function") throw new Error( "No callback provided to asyncMapOrdered") if (typeof fn !== "function") throw new Error( "No map function provided to asyncMapOrdered") if (list === undefined || list === null) return cb_(null, []) if (!Array.isArray(list)) list = [list] if (!list.length) return cb_(null, []) var errState = null , l = list.length , a = l , res = [] , resCount = 0 , maxArgLen = 0 function cb (index) { return function () { if (errState) return var er = arguments[0] var argLen = arguments.length maxArgLen = Math.max(maxArgLen, argLen) res[index] = argLen === 1 ? [er] : Array.apply(null, arguments) // see if any new things have been added. if (list.length > l) { var newList = list.slice(l) a += (list.length - l) var oldLen = l l = list.length process.nextTick(function () { newList.forEach(function (ar, i) { fn(ar, cb(i + oldLen)) }) }) } if (er || --a === 0) { errState = er cb_.apply(null, [errState].concat(flip(res, resCount, maxArgLen))) } }} // expect the supplied cb function to be called // "n" times for each thing in the array. list.forEach(function (ar) { steps.forEach(function (fn, i) { fn(ar, cb(i)) }) }) } function flip (res, resCount, argLen) { var flat = [] // res = [[er, x, y], [er, x1, y1], [er, x2, y2, z2]] // return [[x, x1, x2], [y, y1, y2], [undefined, undefined, z2]] slide-flow-control-1.1.6/lib/async-map.js000066400000000000000000000026771240470157400202560ustar00rootroot00000000000000 /* usage: // do something to a list of things asyncMap(myListOfStuff, function (thing, cb) { doSomething(thing.foo, cb) }, cb) // do more than one thing to each item asyncMap(list, fooFn, barFn, cb) */ module.exports = asyncMap function asyncMap () { var steps = Array.prototype.slice.call(arguments) , list = steps.shift() || [] , cb_ = steps.pop() if (typeof cb_ !== "function") throw new Error( "No callback provided to asyncMap") if (!list) return cb_(null, []) if (!Array.isArray(list)) list = [list] var n = steps.length , data = [] // 2d array , errState = null , l = list.length , a = l * n if (!a) return cb_(null, []) function cb (er) { if (er && !errState) errState = er var argLen = arguments.length for (var i = 1; i < argLen; i ++) if (arguments[i] !== undefined) { data[i - 1] = (data[i - 1] || []).concat(arguments[i]) } // see if any new things have been added. if (list.length > l) { var newList = list.slice(l) a += (list.length - l) * n l = list.length process.nextTick(function () { newList.forEach(function (ar) { steps.forEach(function (fn) { fn(ar, cb) }) }) }) } if (--a === 0) cb_.apply(null, [errState].concat(data)) } // expect the supplied cb function to be called // "n" times for each thing in the array. list.forEach(function (ar) { steps.forEach(function (fn) { fn(ar, cb) }) }) } slide-flow-control-1.1.6/lib/bind-actor.js000066400000000000000000000005761240470157400204040ustar00rootroot00000000000000module.exports = bindActor 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)) } } slide-flow-control-1.1.6/lib/chain.js000066400000000000000000000012241240470157400174330ustar00rootroot00000000000000module.exports = chain var bindActor = require("./bind-actor.js") chain.first = {} ; chain.last = {} function chain (things, cb) { var 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) { if (er) return cb(er, res) if (data !== undefined) res = res.concat(data) LOOP(i + 1, len) }) })(0, things.length) } slide-flow-control-1.1.6/lib/slide.js000066400000000000000000000001711240470157400174510ustar00rootroot00000000000000exports.asyncMap = require("./async-map") exports.bindActor = require("./bind-actor") exports.chain = require("./chain") slide-flow-control-1.1.6/package.json000066400000000000000000000010341240470157400175320ustar00rootroot00000000000000{ "name": "slide", "version": "1.1.6", "author": "Isaac Z. Schlueter (http://blog.izs.me/)", "contributors": [ "S. Sriram (http://www.565labs.com)" ], "description": "A flow control lib small enough to fit on in a slide presentation. Derived live at Oak.JS", "main": "./lib/slide.js", "dependencies": {}, "devDependencies": {}, "engines": { "node": "*" }, "repository": { "type": "git", "url": "git://github.com/isaacs/slide-flow-control.git" }, "license": "ISC" }