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 aflows.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 useFunction#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!
For reference,
logger.ifError
is just a function that returns a callback function for logging errors.(And
logger
is a Winston instance.)