generator-async
Flexible generator-based asynchronous control flow for Node.js.
Introduction
With generator-async
use both callback-based and promise-based libraries all together with a clean, consistent interface. Use the yield
keyword before an asynchronous function call that would normally take a callback or return a promise, and skip the callback or call to then()
.
var async = ; // load `fs` with async's `require`var fs = async; async;
In this example, the contents of /etc/passwd
are read from disk and assigned to passwd
. Program execution waits on the I/O and assignment, while the process event loop keeps on moving. That's in contrast to fs.readFileSync
for example, where the entire process waits.
Notice fs
was loaded through async.require
, which wraps async methods to be compatible. If you'd rather not wrap, you can load modules directly like normal, and just refer to async.cb
wherever a standard callback would be expected. See the Alternative Explicit Callback Interface section below for more.
Running in Parallel
Run functions concurrently with async.parallel
and then yield in the same order.
async;
Examples
Working with Request
var request = async; async;
Working with Redis
var redis = async; async;
Working with Express
Use generator functions directly as route handlers.
var express = async;var fs = async; var app = ; app;
Working with the file system
var fs = async;var mkdirp = async;var rimraf = async;var path = ;var assert = ; async;
Wrapping Classes
Wrapped classes that define methods as generator functions will automatically have those generator functions wrapped in a generator-async context, meaning they'll be invoked and executed when called by consumers.
Consider a Class representing a file:
var { thisinitialize;}; Fileprototype = { thisfilename = filename; } { var contents = fs; return contents; } { var stat = fs; return statsize; }; File = asyncFile;
Consume this functionality in an async.run
context:
async;
Or, consume this functionality as-is just like normal with conventional callbacks.
var file = '/etc/passwd';filesize { console;};
Alternative Explicit Callback Interface
Sometimes it's not feasible to wrap a module or method to be yieldable since it may have a non-standard callback scheme. Or you may prefer the more verbose interface in order to use the module directly and shed some of the intermediary magic. In that case, node's require
like normal, and refer to async.cb
where a callback is expected:
// require `fs` directlyvar fs = ; async;
Use async.cb
where a standard node callback would be expected (a function that accepts err
and data
parameters). For non-standard callbacks refer to async.raw
to get back all the values (and handle errors yourself).
API
async(input)
Implementation depends on the type of input. Given a function, or generator, returns a function that is both yieldable in an async.run
context, and also compatible with a standard callback interface. Given an object or class, returns the input with each of its methods wrapped to be yieldable. Aliased as async.wrap
.
async.require(module, [hints])
Imports a module Like node's native require
, but also wraps that module so that its methods are yieldable in an async.run
context with no callbacks necessary.
That's the goal at least. Wrapping involves making a heuristic best guess about which methods are asynchronous and what is their callback signature etc. So while it often works well, you may sometimes need to give hints about the makeup of the module. See module-async-map for more.
If you'd rather, feel free to use node's native require
instead, and refer to async.cb
where a callback is expected.
async.run(fn*)
Invokes and executes the supplied generator function.
async.proxy(fn)
Returns a wrapped version of the supplied function compatible to be run either in an async.run
context or standard node callback style.
async.fn(fn*)
Returns a function that when called will invoke and execute the supplied generator function.
Collection Methods
Since the yield
keyword is only valid directly inside of generator functions, we can't yield
inside of stock Array
methods, which might be exactly what you want to do sometimes. Instead, use these collection methods, which accept generator functions as iterator functions, so you can yield
from within them. Underlying implementations courtesy of the fantastic async library, which has more documentation.
async.forEach(arr, fn*)
Applies fn*
as an iterator to each item in arr
, in parallel. Aliased as async.each
.
var fs = async; async;
async.eachLimit(arr, limit, fn*)
Same as async.forEach
except that only limit
iterators will be simultaneously running at any time.
async.map(arr, fn*)
Produces a new array of values by mapping each value in arr through the generator function.
async;
async.mapLimit(arr, limit, fn*)
Same as async.map
except that only limit iterators will be simultaneously running at any time.
async.filter(arr, fn*)
Returns a new array of all the values in arr which pass an async truth test.
async;
async.reject(arr, fn*)
The opposite of async.filter
. Removes values that pass an async truth test.
async.reduce(arr, memo, fn*)
Reduces arr
into a single value using an async iterator to return each successive step. memo
is the initial state of the reduction. Runs in series.
async.reduceRight(arr, memo, fn*)
Same as async.reduce
, only operates on arr in reverse order.
async.detect(arr, fn*)
Returns the first value in arr
that passes an async truth test. The generator function is applied in parallel, meaning the first iterator to return true will itself be returned.
async.sortBy(arr, fn*)
Sorts a list by the results of running each arr value through an async generator function.
async;
async.some(arr, fn*)
Returns true if at least one element in the arr satisfies an async test.
async.every(arr, fn*)
Returns true if every element in arr satisfies an async test.
async.concat(arr, fn*)
Applies iterator to each item in arr, concatenating the results. Returns the concatenated list.
History and Inspiration
This is an evolution of gx, with inspiration from other generator-based control flow libraries such as co, genny, galaxy, and suspend.
License
Copyright (c) 2015 David Chester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.