A simple javascript/node.js base type supporting before and after hooks over functions.
or should I say, yet another...
You may have landed here while looking for the widely used hooks
module. This is a clean-room implementation, similar in scope, that I put together as a base class used across many of my other modules. Of note are the options related to which methods are eligible for hooks.
##Features
- Derived from EventEmitter; intended to be used as a base type.
- Methods on derived types can be hooked with
before
andafter
hooks. - Control over which methods are eligible for each type of hook.
- Hooked methods will raise
error
events upon error. - Hooked methods will raise
canceled
events upon cancelation. - Middleware pipelines are immutable at the time of execution (copy-on-write).
before
predicate hook
- indicates whether the method pipeline should continue.pipeline hook
- observes and potentially transforms arguments before continuing the method pipeline.
after
pipeline hook
- observes and potentially transforms the method's result before continuing the method pipeline.observer hook
- observes a method's result.
A predicate hook determines whether a method pipeline continues to process before
the target method. Any hook can act as a predicate by returning a defined falsy value. If a hook's return value is undefined it is not treated as a predicate.
A pipeline hook is added to a method's processing pipeline, either before
or after
the method.
Pipeline hooks before
a method must have the same signature as the hooked method.
Pipeline hooks after
a method must have the signature function (err, res, next) { /*...*/ }
where err
, if truthy, is an error produced by the operation, res
is an anticipated result, and next
is the next method in the pipeline.
If any pipeline hook fails to call the next
method that it is given, it has, in effect, silently canceled the operation.
Every hook is an observer of either the arguments before
a target method, or the result after
. However, if an after
hook has an arity less than 3 it is assumed to be an observer. These hooks are called as synchronous participants in the method pipeline.
If an observer hook throws, the error is captured and communicated to the caller-supplied callback as an error.
Methods that take a callback
as the last argument, and have an arity 1 or 2, are eligible for hooks. The two supported signatures are:
function(callback) { /*...*/ }
function(any, callback) { /*...*/ }
npm install hooked
Tests are written using vows & should.js (you may need to install them). If you've installed in a development environment you can use npm or node to run the tests.
npm test hooked
... or from the hooked directory...
node test
hooked
exports one type called Hooked
. It is entended as a base type, and you'll have to create a type that inherits from it in order to start working with hooks.
var hooked = require('hooked');
// assumes your object has a `perform` method...
my.before('perform', function(obj, next) {
// do something interesting.
next(null, obj);
});
// assumes your object has a `perform` method...
my.after('perform', function(err, res, next) {
// do something interesting.
next(null, res);
});
Well-behaved hooks should call next
or return false
. Returning false
cancels the operation and emits a canceled
event.
// assumes your object has a `perform` method
my.before('perform', function(obj, next) {
// assumes there is a `shouldCancel` method in the current scope
if (shouldCancel('perform', obj)) {
// as a predicate, returning `false` cancels.
return false;
} else {
next(null, obj);
}
});
Once a method has been hooked, unless it is configured for noevents
, an event will be emitted. The event's name matches the method's name.
Events
error
- emitted when a hook throws an error.canceled
- emitted when abefore
hook canceles an operation.{method}
- emitted when the target method completes, beforeafter
hooks.
Events can be enabled before hooks are present by using the enableMethodEvents
method.
// enable an event for the assumed `perfrom` method
my.enableMethodEvents(["perform"]);
// hook up an event handler...
my.on('perform', function (res){ /* hrm, something just occurred */});
Methods that would normally be eligible for hooks based on their signature can be made ineligible by constructor option.
Methods may be marked ineligible for any hook throught the unhooked
option.
var util = require('util')
, Hooked = require('hooked').Hooked
;
function My() {
// instruct the base class that `perform` will be ineligible for hooks...
My.super_.call(this, { unhooked: ["perform"] });
this.perform = function(any, callback) {
/* ... elided ... */
callback(null, 'ok');
};
}
util.inherits(My, Hooked);
Methods may be marked ineligible for before
hooks throught the nonbefore
option.
var util = require('util')
, Hooked = require('hooked').Hooked
;
function My() {
// instruct the base class that `perform` will be ineligible for `before` hooks...
My.super_.call(this, { nonbefore: ["perform"] });
this.perform = function(any, callback) {
/* ... elided ... */
callback(null, 'ok');
};
}
util.inherits(My, Hooked);
Methods may be marked ineligible for after
hooks throught the nonafter
option.
var util = require('util')
, Hooked = require('hooked').Hooked
;
function My() {
// instruct the base class that `perform` will be ineligible for `after` hooks...
My.super_.call(this, { nonafter: ["perform"] });
this.perform = function(any, callback) {
/* ... elided ... */
callback(null, 'ok');
};
}
util.inherits(My, Hooked);
By default, the base class will emit events for hooked methods.
The events can be suppressed through the noevents
option.
var util = require('util')
, Hooked = require('hooked').Hooked
;
function My() {
// instruct the base class not to emit a `perform` event...
My.super_.call(this, { noevent: ["perform"] });
this.perform = function(any, callback) {
/* ... elided ... */
callback(null, 'ok');
};
}
util.inherits(My, Hooked);