Skip to content

Commit

Permalink
1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Havvy committed Jan 17, 2014
0 parents commit 85c5102
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
97 changes: 97 additions & 0 deletions after-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
*
* Event Emitter
*
* Publisher/Subscriber Pattern implementation.
*
* Differences from Node's EventEmitter:
* .after(cb) method (see below)
* Listeners that throw errors are caught and logged to console.
* Listeners happen during their own turn.
* Listener can only listen a maximum of one time with .on()
* "removeListener" is called "off"
* No way to see if the listener is listening. (unused)
* No "addListener" alias
* No domains (unused)
* No erroring on unhandled error event. (unused)
* No maximum listener count. (unused)
* No prototype/No `new` needed to create.
*
* after(callback: function (err: Error U undefined, res: Any U undefined, type: String, ...args: Any)): undefined
* callback is ran after every listener returns.
* First parameter to the callback is the error of the listener, if there is one.
* Second parameter to the callback is the return value of the listener, if there is one.
* The rest of the parameters are the parameters sent to the .emit() that triggered the listener.
*
* If you need one of the features this lacks that is marked unused, feel free to send a pull request/file an issue.
*/


const Set = require('simplesets').Set; // Still waiting on that ES6 Set API.
const Promise = require('bluebird');

const EventEmitter = function () {
const events = {}; // Map (Set Fn)
var postListenerCallbacks = []; // [Error -> any -> string -> ...any -> void]

return {
on: function (type, listener) {
if (!events[type]) {
events[type] = new Set();
}

events[type].add(listener);
},
once: function (type, listener) {
const that = this;

function o () {
that.off(type, o);
return listener.apply(null, Array.prototype.slice.call(arguments));
}

this.on(type, o);
},
off: function (type, listener) {
if (events[type]) {
events[type].remove(listener);
}
},
emit: function (type) {
const args = Array.prototype.slice.call(arguments, 1);

if (!events[type]) {
return;
}

events[type].each(function (listener) {
setImmediate(function () {
Promise.try(listener, args)
// Catch both return results and errors thrown in listener.
.then(function (res) {
postListenerCallbacks.forEach(function (lcb) {
lcb.apply(null, [undefined, res, type].concat(args));
});
}, function (err) {
postListenerCallbacks.forEach(function (lcb) {
lcb.apply(null, [err, undefined, type].concat(args));
});
})
// Catch errors in after chain
.catch(function (err) {
console.log(err.name);
console.log(err.stack);
throw err;
})
.done();
});
});
},

after: function (callback) {
postListenerCallbacks.push(callback);
}
};
};

module.exports = EventEmitter;
12 changes: 12 additions & 0 deletions fez.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node

var fez = require('fez');
var sweetjs = require('fez-sweet.js');

exports.build = function(rule) {
rule('test.sjs', 'test.js', sweetjs({'modules': ['sweet-bdd'], 'readableNames': true}));
};

exports.default = exports.build;

fez(module);
36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "after-events",
"version": "1.0.0",
"description": "Event Emitter with hooks for listener returnn values",
"main": "after-events.js",
"scripts": {
"test": "mocha test.js"
},
"repository": {
"type": "git",
"url": "git://github.com/Havvy/after-events.git"
},
"keywords": [
"events",
"event-emitter"
],
"author": "Ryan Scheel",
"license": "ISC",
"bugs": {
"url": "https://github.com/Havvy/after-events/issues"
},
"homepage": "https://github.com/Havvy/after-events",
"dependencies": {
"bluebird": "~1.0.0",
"simplesets": "~1.2.0"
},
"devDependencies": {
"fez": "git://github.com/fez/fez",
"fez-sweet.js": "~0.6.0",
"sweet-bdd": "~1.0.0",
"mocha": "~1.17.0",
"better-assert": "~1.0.0",
"sinon": "~1.7.3",
"deep-eql": "~0.1.3"
}
}
62 changes: 62 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# After Event Emitter

```
npm install after-events
```

Publisher/Subscriber Pattern implementation.

## Differences from Node's EventEmitter

* .after(cb) method (see below)
* Listeners that throw errors are caught and logged to console.
* Listeners happen during their own turn.
* Listener can only listen a maximum of one time with .on()
* "removeListener" is called "off"
* No way to see if the listener is listening. (unused)
* No "addListener" alias
* No domains (unused)
* No erroring on unhandled error event. (unused)
* No maximum listener count. (unused)
* No prototype/No `new` needed to create.

If you need one of the features this lacks that is marked unused, feel free to send a pull request/file an issue.

### after(callback: function (err: Error U undefined, res: Any U undefined, type: String, ...args: Any)): undefined

* callback is ran after every listener returns.
* First parameter to the callback is the error/rejected value of the listener, if there is one.
* Second parameter to the callback is the return value of the listener, if there is one.
* The rest of the parameters are the parameters sent to the .emit() that triggered the listener.
* Calling it multiple times adds more callbacks to be called in order of addition.

### Example

```
// This is really contrived!
var eventEmitter = require('after-events');
var format = require('util').format;
eventEmitter.after(function (err, ret, eventname, eventarg) {
console.log(format('%s + 1 = %s', eventarg, ret));
});
eventEmitter.on('number', function (number) {
return number + 1;
});
eventEmitter.emit('number', 2);
// Console logs '2 + 1 = 3'
eventEmitter.emit('number', 5);
// Console.logs '5 + 1 = 6'
```

For a realistic example, see [https://github.com/Tennu/tennu/blob/master/lib/command-handler.js](Tennu's Command Handler).

### Tests and Building

To build the test file, use `./fez.js`.

To run the test file, use `mocha test.js`.
82 changes: 82 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const sinon = require('sinon');
const assert = require('better-assert');
const equal = require('deep-eql');
const inspect = require('util').inspect;
const format = require('util').format;
const debug = false;
const logfn = debug ? console.log.bind(console) : function () {
};
const EventEmiter = require('./after-events.js');
describe('After Event Emitter', function () {
var EE;
beforeEach(function () {
logfn();
EE = EventEmiter();
});
it('works as an event emitter.', function (done) {
EE.on('x', function (arg1, arg2) {
assert(arg1 === true);
assert(arg2 === false);
done();
});
EE.emit('x', true, false);
});
it('does not throw on non-existent events.', function (done) {
EE.emit('y');
done();
});
describe('#after', function () {
it('takes a function, which it calls after the listener returns.', function (done) {
EE.on('x', function () {
return true;
});
EE.after(function (err, ret, emitted, arg1, arg2) {
assert(err === undefined);
assert(ret === true);
assert(emitted === 'x');
assert(arg1 === true);
assert(arg2 === false);
done();
});
EE.emit('x', true, false);
});
it('passes the error to err if an error is thrown', function (done) {
const error = new Error();
EE.on('x', function () {
throw error;
});
EE.after(function (err, ret, emitted) {
assert(err === error);
assert(ret === undefined);
done();
});
EE.emit('x');
});
it('can take multiple functions, and call them in order', function (done) {
var callCount = 0;
EE.on('x', function () {
console.log('My!');
return true;
});
EE.after(function (err, ret, emitted) {
console.log('Hi!');
try {
assert(callCount === 0);
callCount += 1;
} catch (e) {
done(e);
}
});
EE.after(function (err, ret, emitted) {
console.log('Bye!');
try {
assert(callCount === 1);
done();
} catch (e) {
done(e);
}
});
EE.emit('x');
});
});
});
84 changes: 84 additions & 0 deletions test.sjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const sinon = require('sinon');
const assert = require('better-assert');
const equal = require('deep-eql');
const inspect = require('util').inspect;
const format = require('util').format;

const debug = false;
const log = debug ? console.log.bind(console) : function () {};

const EventEmiter = require('./after-events.js');

describe 'After Event Emitter' {
var EE;

beforeEach {
log(/* newline */);
EE = EventEmiter();
}

it 'works as an event emitter.' (done) {
EE.on('x', function (arg1, arg2) {
assert(arg1 === true);
assert(arg2 === false);
done()
});

EE.emit('x', true, false);
}

it 'does not throw on non-existent events.' (done) {
EE.emit('y');
done();
}

describe '#after' {
it 'takes a function, which it calls after the listener returns.' (done) {
EE.on('x', function () {return true;});
EE.after(function (err, ret, emitted, arg1, arg2) {
assert(err === undefined);
assert(ret === true);
assert(emitted === 'x');
assert(arg1 === true);
assert(arg2 === false);
done();
});
EE.emit('x', true, false);
}

it 'passes the error to err if an error is thrown' (done) {
const error = new Error();
EE.on('x', function () {throw error});
EE.after(function (err, ret, emitted) {
assert(err === error);
assert(ret === undefined);
done();
});
EE.emit('x');
}

it 'can take multiple functions, and call them in order' (done) {
var callCount = 0;

EE.on('x', function () { return true; });
EE.after(function (err, ret, emitted) {
try {
assert(callCount === 0);
callCount += 1;
} catch (e) {
done(e);
}
});
EE.after(function (err, ret, emitted) {
try {
assert(callCount === 1);
done();
} catch (e) {
done(e);
}
});

EE.emit('x');
}
}
}

0 comments on commit 85c5102

Please sign in to comment.