Wondering what’s next for npm?Check out our public roadmap! »

    slide

    1.1.6 • Public • Published

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

    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
    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
    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
    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.

    Keywords

    none

    Install

    npm i slide

    DownloadsWeekly Downloads

    2,449,596

    Version

    1.1.6

    License

    ISC

    Last publish

    Collaborators

    • avatar
    • avatar
    • avatar
    • avatar
    • avatar
    • avatar
    • avatar