+ All Categories
Home > Documents > Nodejs Controlling Flow

Nodejs Controlling Flow

Date post: 06-Apr-2018
Category:
Upload: venkat-bitla
View: 231 times
Download: 0 times
Share this document with a friend

of 63

Transcript
  • 8/3/2019 Nodejs Controlling Flow

    1/63

    Controlling Flowcallbacks made are easy

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    2/63

    EventEmitters are Easy

    Responding to events is a solved problem(At least for JavaScripters)

    Very similar to DOM coding

    thing.on("event", doSomething)

    easy.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    3/63

    callbacks are hard Most common complaint about nodejs:

    Ew, callbacks? Ugly nasty nesting indented

    forever spaghetti code? Just to open a le?!YOUVE GOTTA BE FRICKIN KIDDING ME.

    Variant:

    Why doesnt this work?

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    4/63

    It's worse than that

    Most *objects* in NodeJS are EventEmitters (http server/client, etc.)

    Most low level *functions* take callbacks.(posix API, DNS lookups, etc.)

    Beyond "hello, world" it gets tricky.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    5/63

    What's actually hard?

    Doing a bunch of things in a specic order. Knowing when stuff is done. Handling failures.

    Breaking up functionality into parts(innitely nested inline callbacks)

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    6/63

    Common Mistakes

    Abandoning convention and consistency.

    Putting all callbacks inline. Using libraries without grokking them. Trying to make async code look sync.*

    *controversialIn 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 totalk about a "Promise" like it's one kind of thing, when it's actually a very general pattern.Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    7/63

    ow control libraries

    There are approximately 7 gillion owcontrol libraries in the node.js ecosystem.

    Everyone likes their own the best.

    Obvious solution: Write your own.

    Let's do that now.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    8/63

    This is a learning

    exercise The goal is not to write the ideal ow

    control library.

    The goal is simplicity and understanding,with very little magic.

    Please dont hold questions for the end. Please do try this at home.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    9/63

    First Priority:

    A Reall Cool Name Be descriptive, but not much to describe. So minimal, you can write it in a slide show. http://github.com/isaacs/slide-ow-control

    npm install slide

    Hellz. Yeah.

    Thursday, October 28, 2010

    http://github.com/isaacs/slide-flow-controlhttp://github.com/isaacs/slide-flow-controlhttp://github.com/isaacs/slide-flow-control
  • 8/3/2019 Nodejs Controlling Flow

    10/63

    Dene Conventions Two kinds of functions:

    Actors: Take action

    Callbacks: Get results

    Essentially the continuation pattern.Resulting code *looks* similar to bers, but

    is *much* simpler to implement.

    Bonus: node works this way in the lowlevelAPIs already, and it's very exible.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    11/63

    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 withmodied data, or to pass errors upwards.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    12/63

    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

  • 8/3/2019 Nodejs Controlling Flow

    13/63

    Actor Examplefunction 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)}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    14/63

    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())

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    15/63

    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())

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    16/63

    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())

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    17/63

    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() )

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    18/63

    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")

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    19/63

    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")

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    20/63

    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")

    })}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    21/63

    usecase: asyncMap I have a list of 10 les, and need to read all

    of them, and then continue when they're alldone.

    I have a dozen URLs, and need to fetchthem all, and then continue when they're alldone.

    I have 4 connected users, and need to senda message to all of them, and then continuewhen that's done.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    22/63

    usecase: asyncMap

    I have a list of n things, and I need todosomething with all of them, in parallel, andget the results once they're all complete.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    23/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction 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

  • 8/3/2019 Nodejs Controlling Flow

    24/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction cb (er, data) {

    if (errState) returnif (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

  • 8/3/2019 Nodejs Controlling Flow

    25/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction cb (er, data) {

    if (errState) returnif (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

  • 8/3/2019 Nodejs Controlling Flow

    26/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction cb (er, data) {

    if (errState) returnif (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

  • 8/3/2019 Nodejs Controlling Flow

    27/63

    usecase: asyncMap

    function writeFiles (files, what, cb) {asyncMap( files

    , function (f, cb) {fs.writeFile(f,what,cb)}

    , cb

    )}

    writeFiles([my,file,list], "foo", cb)

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    28/63

    asyncMap

    note that asyncMap itself is an Actorfunction, so you can asyncMap yourasyncMaps, dawg.

    This implementation is ne if order doesn'tmatter, but what if it does?

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    29/63

    asyncMap - ordered

    close over the array index in the generatedcb function.

    match up results to their original index.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    30/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction cbGen (i) {

    return function cb (er, data) {if (errState) returnif (er) return cb(errState = er)results[i] = dataif (-- n === 0)

    return cb_(null, results)}}

    list.forEach(function (l, i ) {fn(l, cbGen(i) )

    })

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    31/63

    function asyncMap (list, fn, cb_) {var n = list.length

    , results = [], errState = nullfunction cbGen (i) {

    return function cb (er, data) {if (errState) returnif (er) return cb(errState = er)results[i] = dataif (-- n === 0)

    return cb_(null, results)}}

    list.forEach(function (l, i) {fn(l, cbGen(i))

    })

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    32/63

    usecase: chain

    I have to do a bunch of things, in order. Getdb credentials out of a le, read the datafrom the db, write that data to another le.

    If anything fails, do not continue.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    33/63

    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)

    }

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    34/63

    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)

    }

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    35/63

    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)

    }

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    36/63

    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)

    }

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    37/63

    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)}"

    Results are discarded, which is a bit lame.

    No way to branch.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    38/63

    reducing boilerplate

    convert an array of [fn, args] to an actorthat takes no arguments (except cb)

    A bit like Function#bind, but tailored forour use-case.

    bindActor(obj, "method", a, b, c)bindActor(fn, a, b, c)bindActor(obj, fn, a, b, c)

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    39/63

    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

  • 8/3/2019 Nodejs Controlling Flow

    40/63

    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

  • 8/3/2019 Nodejs Controlling Flow

    41/63

    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

  • 8/3/2019 Nodejs Controlling Flow

    42/63

    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

  • 8/3/2019 Nodejs Controlling Flow

    43/63

    bindActor

    Some obvious areas for improvement. They wouldn't t on a slide.

    Left as an exercise for the reader.

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    44/63

    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)}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    45/63

    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)}

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    46/63

    chain: branching

    Skip over falsey arguments chain([ doThing && [thing,a,b,c] , isFoo && [doFoo, "foo"]

    , subChain &&

    [chain, [one, two]] ], cb)

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    47/63

    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)

    }

    Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    48/63

    chain: tracking results

    Supply an array to keep the results in.

    If you don't care, don't worry about it.

    Last result is always inresults[results.length - 1]

    Just for kicks, let's also treat chain.rst andchain.last as placeholders for the rst/lastresult up until that point.

    Thursday, October 28, 2010

    chain.first = {} ; chain.last = {}

  • 8/3/2019 Nodejs Controlling Flow

    49/63

    {} ; {}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 = {}

  • 8/3/2019 Nodejs Controlling Flow

    50/63

    ;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 = {}

  • 8/3/2019 Nodejs Controlling Flow

    51/63

    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 = {}

  • 8/3/2019 Nodejs Controlling Flow

    52/63

    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 = {}

  • 8/3/2019 Nodejs Controlling Flow

    53/63

    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 = {}

  • 8/3/2019 Nodejs Controlling Flow

    54/63

    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) }

    Ok, this can't get any

    bigger or it won't t.Thursday, October 28, 2010

  • 8/3/2019 Nodejs Controlling Flow

    55/63

    Non-trivial Use Case

    Read number les in a directory

    Add the results together Ping a web service with the result

    Write the response to a le

    Delete the number les

    Thursday, October 28, 2010

    var chain = require("./chain.js")

  • 8/3/2019 Nodejs Controlling Flow

    56/63

    , 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")

  • 8/3/2019 Nodejs Controlling Flow

    57/63

    , 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")

  • 8/3/2019 Nodejs Controlling Flow

    58/63

    , 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")

  • 8/3/2019 Nodejs Controlling Flow

    59/63

    , 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")

  • 8/3/2019 Nodejs Controlling Flow

    60/63

    , 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")

  • 8/3/2019 Nodejs Controlling Flow

    61/63

    , 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")( )

  • 8/3/2019 Nodejs Controlling Flow

    62/63

    , 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

  • 8/3/2019 Nodejs Controlling Flow

    63/63

    Convention Prots

    Consistent API from top to bottom. Sneak in at any point to inject functionality.

    (testable, reusable, etc.)

    When ruby and python users whine, youcan smile condescendingly.


Recommended