+23
-4
@@ -66,6 +66,6 @@ var async = require('async'), | ||
| if (callback) { | ||
| asyncApply(emitter, listeners[0], args, callback); | ||
| asyncApply(emitter, handleOnce(emitter, name, listeners[0]), args, callback); | ||
| } | ||
| else { | ||
| return listeners[0].apply(emitter, args); | ||
| return handleOnce(emitter, name, listeners[0]).apply(emitter, args); | ||
| } | ||
@@ -79,2 +79,5 @@ } | ||
| function asyncApply (thisArg, fn, args, done) { | ||
| if ('function' === typeof fn.removeWrapper) { | ||
| fn.removeWrapper(); // remove the wrapper, as it would have removed itself | ||
| } | ||
| if (!Array.isArray(args)) args = [args]; | ||
@@ -99,6 +102,22 @@ if (fn.length <= args.length) { | ||
| // For waterfall, args only need to be bound to the first task. | ||
| return asyncApply.bind(emitter, emitter, listener); | ||
| return asyncApply.bind(emitter, emitter, handleOnce(emitter, name, listener)); | ||
| } | ||
| return asyncApply.bind(emitter, emitter, listener, args); | ||
| return asyncApply.bind(emitter, emitter, handleOnce(emitter, name, listener), args); | ||
| }); | ||
| } | ||
| /** | ||
| * Allow (and honor) emitter.once('foo', ...) | ||
| * See: | ||
| * EventEmitter.prototype.once | ||
| * https://github.com/joyent/node/blob/master/lib/events.js#L184-L199 | ||
| */ | ||
| function handleOnce (emitter, name, listener) { | ||
| // A .once listener is actually a wrapper that has the original listener attached | ||
| // If there is no such property, it's a normal .on listener -- proceed as normal | ||
| if (typeof listener.listener !== 'function') return listener; | ||
| var origlistener = listener.listener; | ||
| origlistener.removeWrapper = emitter.removeListener.bind(emitter, name, listener); // save this removal function for execution time | ||
| return origlistener; // apply to the original listener; note that since the .once wrapper | ||
| // was removed, it won't get invoked again | ||
| } |
+1
-1
| { | ||
| "name": "eventflow", | ||
| "version": "0.0.10", | ||
| "version": "0.0.11", | ||
| "description": "Flow control for your event emitters", | ||
@@ -5,0 +5,0 @@ "main": "eventflow.js", |
+174
-8
@@ -30,7 +30,29 @@ var eventflow = require('../') | ||
| emitter.once('foo', function () { | ||
| result.push('d'); | ||
| }); | ||
| emitter.once('foo', function (cb) { | ||
| result.push('e'); | ||
| cb(); | ||
| }); | ||
| assert.equal(emitter.listeners('foo').length, 5); | ||
| emitter.series('foo', function () { | ||
| assert.equal(emitter.listeners('foo').length, 3); | ||
| assert.equal(result[0], 'a'); | ||
| assert.equal(result[1], 'b'); | ||
| assert.equal(result[2], 'c'); | ||
| done(); | ||
| assert.equal(result[3], 'd'); | ||
| assert.equal(result[4], 'e'); | ||
| result = []; | ||
| emitter.series('foo', function () { | ||
| assert.equal(emitter.listeners('foo').length, 3); | ||
| assert.equal(result[0], 'a'); | ||
| assert.equal(result[1], 'b'); | ||
| assert.equal(result[2], 'c'); | ||
| assert.equal(result.length, 3); | ||
| done(); | ||
| }); | ||
| }); | ||
@@ -51,3 +73,17 @@ }); | ||
| emitter.once('bar', function (a, b) { | ||
| result.push(a); | ||
| result.push(b); | ||
| }); | ||
| emitter.once('bar', function (a, b, cb) { | ||
| result.push(a); | ||
| result.push(b); | ||
| cb(); | ||
| }); | ||
| assert.equal(emitter.listeners('bar').length, 4); | ||
| emitter.series('bar', 'foo', 'baz', function () { | ||
| assert.equal(emitter.listeners('bar').length, 2); | ||
| assert.equal(result[0], 'foo'); | ||
@@ -57,3 +93,16 @@ assert.equal(result[1], 'baz'); | ||
| assert.equal(result[3], 'baz'); | ||
| done(); | ||
| assert.equal(result[4], 'foo'); | ||
| assert.equal(result[5], 'baz'); | ||
| assert.equal(result[6], 'foo'); | ||
| assert.equal(result[7], 'baz'); | ||
| result = []; | ||
| emitter.series('bar', 'foo', 'baz', function () { | ||
| assert.equal(emitter.listeners('bar').length, 2); | ||
| assert.equal(result[0], 'foo'); | ||
| assert.equal(result[1], 'baz'); | ||
| assert.equal(result[2], 'foo'); | ||
| assert.equal(result[3], 'baz'); | ||
| assert.equal(result.length, 4); | ||
| done(); | ||
| }); | ||
| }); | ||
@@ -69,2 +118,8 @@ }); | ||
| }); | ||
| emitter.once('fruit', function (cb) { | ||
| cb(null, 'grape'); | ||
| }); | ||
| emitter.once('fruit', function () { | ||
| return 'lime'; | ||
| }); | ||
| emitter.series('fruit', function (err, results) { | ||
@@ -74,2 +129,4 @@ assert.ifError(err); | ||
| assert.equal(results[1], 'orange'); | ||
| assert.equal(results[2], 'grape'); | ||
| assert.equal(results[3], 'lime'); | ||
| done(); | ||
@@ -83,2 +140,5 @@ }); | ||
| }); | ||
| emitter.once('drink', function (cb) { | ||
| cb('oh no! first'); | ||
| }); | ||
| emitter.on('drink', function (cb) { | ||
@@ -93,4 +153,10 @@ cb('oh no!'); | ||
| assert.equal(result.length, 1); | ||
| assert.equal(err, 'oh no!'); | ||
| done(); | ||
| assert.equal(err, 'oh no! first'); | ||
| result = []; | ||
| emitter.series('drink', function (err) { | ||
| assert.equal(result[0], 'coke'); | ||
| assert.equal(result.length, 1); | ||
| assert.equal(err, 'oh no!'); | ||
| done(); | ||
| }); | ||
| }); | ||
@@ -100,2 +166,5 @@ }); | ||
| it('should support sync listeners returning errors', function (done) { | ||
| emitter.once('eat', function () { | ||
| return new Error('I am not really hungry...'); | ||
| }); | ||
| emitter.on('eat', function () { | ||
@@ -105,4 +174,7 @@ return new Error('I am full'); | ||
| emitter.series('eat', function (err, results) { | ||
| assert.equal(err.message, 'I am full'); | ||
| done(); | ||
| assert.equal(err.message, 'I am not really hungry...'); | ||
| emitter.series('eat', function (err, results) { | ||
| assert.equal(err.message, 'I am full'); | ||
| done(); | ||
| }); | ||
| }); | ||
@@ -128,5 +200,14 @@ }); | ||
| }); | ||
| emitter.on('candy', function () { | ||
| result.boring = 'Hershey Bar'; | ||
| }); | ||
| emitter.on('candy', function (cb) { | ||
| result.perfect = 'Peanut Butter Cup'; | ||
| cb(); | ||
| }); | ||
| emitter.parallel('candy', function () { | ||
| assert.equal(result.sour, 'Sour Patch'); | ||
| assert.equal(result.hard, 'Jolly Rancher'); | ||
| assert.equal(result.boring, 'Hershey Bar'); | ||
| assert.equal(result.perfect, 'Peanut Butter Cup'); | ||
| done(); | ||
@@ -143,8 +224,17 @@ }); | ||
| }); | ||
| emitter.once('numbers', function() { | ||
| return 3; | ||
| }); | ||
| emitter.once('numbers', function() { | ||
| return 4; | ||
| }); | ||
| emitter.parallel('numbers', function (err, results) { | ||
| assert.ifError(err); | ||
| assert.equal(results[1], 2); | ||
| assert.equal(results[3], 4); | ||
| assert.equal(results.length, 4); | ||
| emitter.parallel('numbers', function (err, results) { | ||
| assert.ifError(err); | ||
| assert.equal(results[0], 1); | ||
| assert.equal(results.length, 2); | ||
| done(); | ||
@@ -177,3 +267,3 @@ }); | ||
| it('should work when there is exactly one synchronous listner', function (done) { | ||
| it('should work when there is exactly one synchronous listener', function (done) { | ||
| var timestamp = new Date().getTime(); | ||
@@ -190,4 +280,19 @@ emitter.on('timestamp', function () { | ||
| it('should work when there is exactly one asynchronous listener', function (done) { | ||
| it('should respect exactly one synchronous .once listener', function (done) { | ||
| var timestamp = new Date().getTime(); | ||
| emitter.once('timestamp', function () { | ||
| return timestamp; | ||
| }); | ||
| emitter.invoke('timestamp', function (err, value) { | ||
| assert.ifError(err); | ||
| assert.equal(value, timestamp); | ||
| emitter.invoke('timestamp', function (err, value) { | ||
| assert(err); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
| it('should respect exactly one asynchronous listener', function (done) { | ||
| var timestamp = new Date().getTime(); | ||
| emitter.on('timestamp', function (callback) { | ||
@@ -203,2 +308,17 @@ callback(null, timestamp); | ||
| it('should respect exactly one asynchronous .once listener', function (done) { | ||
| var timestamp = new Date().getTime(); | ||
| emitter.once('timestamp', function (callback) { | ||
| callback(null, timestamp); | ||
| }); | ||
| emitter.invoke('timestamp', function (err, value) { | ||
| assert.ifError(err); | ||
| assert.equal(value, timestamp); | ||
| emitter.invoke('timestamp', function (err, value) { | ||
| assert(err); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
| it('should work with arguments', function (done) { | ||
@@ -226,2 +346,25 @@ emitter.on('add', function (a, b) { | ||
| it('should work with arguments using .once listener', function (done) { | ||
| emitter.once('multiply', function (a, b) { | ||
| return a * b; | ||
| }); | ||
| emitter.invoke('multiply', 2, 3, function (err, value) { | ||
| assert.ifError(err); | ||
| assert.equal(value, 6); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('should work with arguments, asynchronously, using .once listener', function (done) { | ||
| emitter.once('modulus', function (a, b, callback) { | ||
| // You thought I was going to divide, didn't you? | ||
| callback(null, a % b); | ||
| }); | ||
| emitter.invoke('modulus', 3, 2, function (err, value) { | ||
| assert.ifError(err); | ||
| assert.equal(value, 1); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('should be able to be called multiple times', function (done) { | ||
@@ -247,2 +390,6 @@ emitter.on('echo', function (msg) { | ||
| assert.equal(emitter.invoke('sync'), 'isSync'); | ||
| emitter.once('resync', function () { | ||
| return 'isSync'; | ||
| }); | ||
| assert.equal(emitter.invoke('resync'), 'isSync'); | ||
| }); | ||
@@ -271,2 +418,21 @@ }); | ||
| it('should work with one or more .once handlers', function (done) { | ||
| emitter.once('foo', function (n) { | ||
| return n + 1; | ||
| }); | ||
| emitter.once('foo', function (n, cb) { | ||
| cb(null, n * 5); | ||
| }); | ||
| emitter.on('foo', function (n) { | ||
| return n - 3; | ||
| }); | ||
| emitter.waterfall('foo', 0, function (err, n) { | ||
| assert.equal(n, 2); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('should support optional use of `error`', function (done) { | ||
@@ -273,0 +439,0 @@ emitter.on('drink', function (n) { |
25615
29.3%542
45.7%