wagner-core
v0.2.0
Published
Dependency injector and DI-based async framework for writing modular, durable server code.
Downloads
2,228
Readme
wagner-core
Wagner is primarily designed to be a more elegant take on orchestrator, hence the name. If you've used orchestrator for web apps and found it cumbersome, Wagner is for you.
API
As a dependency injector
Wagner includes a basic dependency injector that provides an API similar to AngularJS 1.x's dependency injector.
It allows you to execute async tasks based on parameter names
You register 'services' with Wagner using the factory()
function.
Services have a unique name - any function you pass through factory()
or invoke()
can list services in its parameter list.
wagner.factory('bacon', function() {
return 'bacon';
});
wagner.factory('breakfast', function(bacon) {
return bacon + ' and eggs';
});
var result = wagner.invoke(function(breakfast) {
assert.equal(breakfast, 'bacon and eggs');
return breakfast;
});
assert.equal(result, 'bacon and eggs');
It allows you to use locals
A local is a value specific to a particular execution of
invoke()
. You can use locals like any other service.
wagner.factory('eggs', function(number) {
return 'finished making ' + number + ' eggs';
});
wagner.invoke(function(eggs) {
assert.equal(eggs, 'finished making 4 eggs');
}, { number: 4 });
It only executes the factory function once
Service functions are only executed once, the value is cached for
all future calls to invoke()
.
var count = 0;
wagner.factory('eggs', function() {
++count;
return 5;
});
assert.equal(count, 0);
wagner.invoke(function(eggs) {
assert.equal(eggs, 5);
assert.equal(count, 1);
});
wagner.invoke(function(eggs) {
assert.equal(count, 1);
});
assert.equal(count, 1);
It allows you to .get()
a dependency
.constant(a, b)
is a convenient shorthand for
.factory(a, function() { return b; }
wagner.constant('eggs', 6);
wagner.task('bacon', function(eggs) {
return Math.floor(eggs / 2);
});
assert.equal(wagner.get('bacon'), 3);
It has a .constant()
function
You can also use .get()
to explicitly get a dependency.
wagner.constant('eggs', 5);
wagner.invoke(function(eggs) {
assert.equal(eggs, 5);
});
As a way to reduce error-handling boilerplate
If you're a NodeJS developer, you've probably gotten sick of writing the following code:
function(error, res) { if (error) { return handleError(error); } }
The wagner.safe()
function helps you make that cleaner.
It wraps callbacks to bubble up errors
wagner.safe()
returns an event emitter that has a try()
function.
Just wrap your callbacks in a try()
and all async errors get deferred
to your event emitter. Like domains, but with less suck.
var safe = wagner.safe();
var asyncOpThatErrors = function(callback) {
setTimeout(function() {
callback('This is an error!');
});
};
asyncOpThatErrors(safe.try(function(error) {
// Never gets called: safe catches the error
assert.ok(false);
}));
safe.on('error', function(error) {
assert.equal(error, 'This is an error!');
done();
});
It catches exceptions too
The try()
function also wraps your callbacks in a try/catch and emits.
any exceptions. Never again will a
TypeError: Cannot read property 'value' of undefined
in your callback crash your server.
var safe = wagner.safe();
var asyncOpThatSucceeds = function(callback) {
setTimeout(function() {
callback();
});
};
asyncOpThatSucceeds(safe.try(function() {
throw 'Oops I messed up';
}));
safe.on('error', function(error) {
assert.equal(error.toString(), 'Oops I messed up');
done();
});
As an async framework
Wagner also includes the ability to execute async tasks in a
dependency-injection-like way. Wagner has 2 functions, invokeAsync()
and task()
, that enable you to write neat modular async code.
It can execute async tasks using invokeAsync()
The task()
and invokeAsync()
are roughly analogous to factory
and invoke()
. There are 3 differences:
- The function you pass to
task()
takes a callback, which it uses to pass a value to dependent tasks. - The function you pass to
invokeAsync()
takes an error, which contains the first error that happened when executing the specified tasks. - Tasks are re-executed every time you call
invokeAsync()
, whereas services are cached forever.
wagner.task('task1', function(callback) {
setTimeout(function() {
callback(null, 'test');
}, 50);
});
wagner.invokeAsync(function(error, task1) {
assert.ok(!error);
assert.equal(task1, 'test');
done();
});
It re-executes tasks on subsequent calls to invokeAsync()
var called = 0;
wagner.task('task1', function(callback) {
++called;
setTimeout(function() {
callback(null, 'test');
}, 0);
});
wagner.invokeAsync(function(error, task1) {
assert.ok(!error);
assert.equal(task1, 'test');
assert.equal(called, 1);
wagner.invokeAsync(function(error, task1) {
assert.ok(!error);
assert.equal(task1, 'test');
assert.equal(called, 2);
done();
});
});
It executes tasks with maximum parallelization
Tasks are executed at most once per call to invokeAsync()
, and tasks
are executed with maximum parallelization. That is, as soon as all a
tasks dependencies are ready, the task executes.
var executed = {};
wagner.task('readFile1', function(callback) {
assert.equal(Object.keys(executed).length, 0);
executed.readFile1 = true;
callback(null, 'test');
});
wagner.task('processFile1', function(readFile1, callback) {
assert.equal(Object.keys(executed).length, 1);
assert.ok(executed.readFile1);
setTimeout(function() {
callback(null, 'test');
}, 5);
});
wagner.task('logFile1', function(readFile1, callback) {
assert.equal(Object.keys(executed).length, 1);
assert.ok(executed.readFile1);
setTimeout(function() {
callback(null, 'test');
}, 5);
});
wagner.invokeAsync(function(error, processFile1, logFile1) {
assert.ifError(error);
done();
});