README
ArtilleryAsync
Common patterns for writing asynchronous code.
Install using npm install artillery-async
Async.js?
Why ArtilleryAsync instead of- Async's API is difficult. Async provides 60+ oddly-named functions with overlapping functionality. ArtilleryAsync is six functions and that's all we've needed after writing 100,000+ lines of JavaScript over four years.
- Async is inconsistent. Sometimes Async calls callbacks synchronously and sometimes it doesn't. ArtilleryAsync does things synchronously when possible. Yes, this means you might overflow the call stack, but ArtilleryAsync provides the choice of when to use
process.nextTick()
orsetImmediate()
. - Async's implementation is unwieldy. We had a lot of difficulty when we tried to debug async. ArtilleryAsync's implementation is only 105 lines of CoffeeScript.
Contents
Functions
async.barrier(count, callback)
count
Numbercallback
Function(err)
Returns a function that executes callback
after being called count
times. The returned function takes no arguments. It's like parallel() but makes code simpler, especially when you don't need to keep track of errors.
Example
var async = require('artillery-async');
function loadDependencies(deps, cb) {
var i, barrier = async.barrier(deps.length, cb);
for (i = 0; i < deps.length; i++) {
(function(dep) {
loadSingleItem(dep, function(err, contents) {
if (err) console.error(dep + " didn't load: " + err);
barrier();
});
})(deps[i]);
}
}
async.series(steps, callback)
steps
Array of Function([args...,] callback)callback
Function(err[, args...])
Runs each function in steps
serially passing any callback arguments to the next function. When the last step has finished, callback
is executed. If any step calls its callback with an error, the final callback
is executed immediately with that error and no further steps are run.
This is the most popular function in this module. It's usually used as a control flow mechanism — the cascading arguments aren't used that often but the technique can come in handy.
Example
var async = require('artillery-async');
async.series([
function(cb) {
fs.exists(path, cb);
},
function(exists, cb) {
if (exists) fs.readFile(path, cb);
},
function(contents, cb) {
request.post('https://example.com/upload', { form: { file: contents } }, cb);
},
function(res, body, cb) {
cb(null, res.statusCode);
}
], function(err, code) {
if (err) {
console.error('Error:', err);
} else {
console.log('Done! Got status code:', code);
}
});
async.parallel(steps, callback)
steps
Array of Function(callback)callback
Function(err)
Runs each function in steps
in parallel. When all steps have finished, callback
is executed. Any errors produced by the steps are accumulated and passed to callback
as an array.
Example
var async = require('artillery-async');
app.get('/home', function(req, res) {
var result = {};
async.parallel([
function getNews(cb) {
db.news.getAll({ limit: 10 }, function(err, items) {
result.news = items;
cb(err);
});
},
function getGames(cb) {
db.games.getAll({ limit: 10 }, function(err, items) {
result.games = items;
cb(err);
});
},
], function(err) {
if (err) {
res.code(500).send(err);
} else {
res.render('home', result);
}
});
});
async.while(condition, iterator, callback)
condition
Function()iterator
Function(callback)callback
Function(err)
Repeatedly calls iterator
while the return value of condition
is true or iterator
calls its callback with and error. Then callback
is called, possibly with an error if iterator
produced one.
Example
var async = require('artillery-async');
var i = 0;
function cond() { return i <= 20; }
function iter(cb) {
db.items.insert({ id: i++ }, cb);
}
async.while(cond, iter, function(err) {
if (err) {
console.error('Insert failed:', err);
} else {
console.log('Done!');
}
});
async.forEachSeries(items, iterator, callback)
items
Arrayiterator
Function(item, callback)callback
Function(err)
Calls iterator
with each item in items
in serial, finally calling callback
at the end. If the iterator
function calls its callback with an error, no more items are processed and callback
is called with the error.
var async = require('artillery-async');
async.forEachSeries(
filesToRemove,
function(filename, cb) {
promptUser('Are you sure you want to delete ' + filename + '?', function(err, choice) {
if (err) return cb(err);
if (choice) {
deleteRecursive(filename, cb);
} else {
cb();
}
});
},
function(err) {
if (err) {
showAlert('Error: ' + err);
}
}
);
async.forEachParallel(items, iterator, [limit,] callback)
items
Arrayiterator
Function(item, callback)limit
(optional) Numbercallback
Function(err)
Calls iterator
with each item in items
in parallel and calls callback
when complete. If limit
is specified, no more than that many iterator
s will be in flight at any time. Any errors produced by the steps are accumulated and passed to callback
as an array.
Example
var async = require('artillery-async');
async.forEachParallel(
filesToUpload,
MAX_UPLOAD_CONCURRENCY, // 10 or so
function(filename, cb) {
fs.readFile(filename, function(err, contents) {
if (err) return cb(err);
s3.putObject({ Key: filename, Body: contents }, cb);
});
},
function(err, code) {
if (err) {
console.error('Error:', err);
} else {
console.log('Done!');
}
}
);
Notes
No mechanisms are provided for controlling the context. If you need the this
variable, you'll need to scope it yourself (var that = this;
) or use Function.bind()
.