artillery-async v1.0.5
ArtilleryAsync
Common patterns for writing asynchronous code.
Install using npm install artillery-async
Why ArtilleryAsync instead of Async.js?
- 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)
countNumbercallbackFunction(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)
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)
stepsArray of Function(callback)callbackFunction(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)
conditionFunction()iteratorFunction(callback)callbackFunction(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)
itemsArrayiteratorFunction(item, callback)callbackFunction(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)
itemsArrayiteratorFunction(item, callback)limit(optional) NumbercallbackFunction(err)
Calls iterator with each item in items in parallel and calls callback when complete. If limit is specified, no more than that many iterators 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().