Dependency injector and dependency-injection-based async framework for NodeJS
APACHE-2.0 License
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.
Wagner includes a basic dependency injector that provides an API similar to AngularJS 1.x's dependency injector.
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');
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 });
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);
.get()
a dependencyYou can also use .get()
to explicitly get a dependency.
wagner.constant('eggs', 6);
wagner.task('bacon', function(eggs) {
return Math.floor(eggs / 2);
});
assert.equal(wagner.get('bacon'), 3);
.constant()
function.constant(a, b)
is a convenient shorthand for
.factory(a, function() { return b; }
wagner.constant('eggs', 5);
wagner.invoke(function(eggs) {
assert.equal(eggs, 5);
});
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.
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();
});
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();
});
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.
invokeAsync()
The task()
and invokeAsync()
are roughly analogous to factory
and invoke()
. There are 3 differences:
task()
takes a callback, which it uses toinvokeAsync()
takes an error, whichinvokeAsync()
, whereas
wagner.task('task1', function(callback) {
setTimeout(function() {
callback(null, 'test');
}, 50);
});
wagner.invokeAsync(function(error, task1) {
assert.ok(!error);
assert.equal(task1, 'test');
done();
});
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();
});
});
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();
});