Skip to content

Instantly share code, notes, and snippets.

@aseemk
Last active December 30, 2015 10:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aseemk/7818408 to your computer and use it in GitHub Desktop.
Save aseemk/7818408 to your computer and use it in GitHub Desktop.
An excerpt of our internal Node.js style guide at @fiftythree, talking about Streamline patterns, tips, and tricks. Tailored for CoffeeScript.

Streamline

We use Streamline.js to manage async control flow in Node. If you're new to Streamline, its introductory blog post frames it well. Be sure to read through the Streamline FAQ as well.

Some noteworthy tips and gotchas:

  • A "Streamline function" means a function written in Streamline syntax. For scripts, the top level counts. (But don't make Streamline calls at the top level in modules, since Node's require() isn't async / won't wait!)

  • When calling a Streamline function, you must pass a callback always. (This wasn't the case pre-Streamline-0.10; you could pass null, or omit the callback altogether. No longer w/ Streamline 0.10.)

    Fortunately, logger.ifError is always available, and it's a great "best practice" to log async errors anyway (so that they're not silently swallowed). And, that works even from non-Streamline functions, e.g. event listeners.

      update = (file, _) ->
          # ...
      file.on 'change', ->
          update file, logger.ifError 'File update failed!'
  • If you want to kick off an async call but still make use of the results later, you can pass !_ as the callback to get a future. The most typical use case for this is parallelization, and Streamline offers a flows.collect utility for doing that directly.

      flows = require 'streamline/lib/util/flows'
      users = flows.collect _,
          User.getById id, !_ for id in userIds
  • If you want to kick off an async task inline, using an IIFE is a nice shorthand approach. Define it with _, but invoke it with a callback:

      # some work before
      # ...
      do (_=logger.ifError 'Inline async task failed!') ->
          waitForSomething _
          thenSomethingElse _
      # ...
      # some work immediately after, not waiting for the above

    (You could also pass !_ instead of a callback to have the IIFE return a future.)

    This is also useful if you want to write some Streamline code but you're in a non-Streamline function. (We don't generally encounter that case, though.)

  • Similarly, if you need external (third-party) code to invoke a Streamline function, you must get it to pass a callback, too. Hopefully the code you're using can do this. E.g. setTimeout() can:

      setTimeout (_) ->
          someAsyncTask _
      , 500, logger.ifError 'Delayed async task failed!'

    If it can't (e.g. process.nextTick()), you need to create a non-Streamline wrapper that calls your Streamline function w/ a callback. Fortunately, you can use Function#bind() to shorthand that:

      process.nextTick ((_) ->
          someAsyncTask _
      ).bind(@, logger.ifError 'Delayed async task failed!')
  • Because _ is a reserved character in Streamline code, we can't use it as the variable for Underscore.js. So we use $ instead, since we don't use jQuery on the server-side, and Underscore is a jQuery-like utility anyway.

If you have any issues or questions, talk to @aseemk!

@aseemk
Copy link
Author

aseemk commented Dec 6, 2013

For reference, logger.ifError is just a function that returns a callback function for logging errors.

logger.ifError = (msg) ->
    (err) ->
        if err
            logger.error msg, err

(And logger is a Winston instance.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment