diff --git a/index.js b/index.js index d0a1b2c..c08f4b9 100644 --- a/index.js +++ b/index.js @@ -1,36 +1,8 @@ 'use strict'; +const assertEventName = require('./util'); const resolvedPromise = Promise.resolve(); -function assertEventName(eventName) { - if (typeof eventName !== 'string') { - throw new TypeError('eventName must be a string'); - } -} - -async function * iterator(emitter, eventName) { - const queue = []; - const off = emitter.on(eventName, data => { - queue.push(data); - }); - - try { - /* eslint-disable no-constant-condition */ - /* eslint-disable no-await-in-loop */ - while (true) { - if (queue.length > 0) { - yield queue.shift(); - } else { - yield await emitter.once(eventName); - } - } - /* eslint-enable no-constant-condition */ - /* eslint-enable no-await-in-loop */ - } finally { - off(); - } -} - module.exports = class Emittery { constructor() { this._events = new Map(); @@ -47,13 +19,8 @@ module.exports = class Emittery { on(eventName, listener) { assertEventName(eventName); - - if (typeof listener === 'function') { - this._getListeners(eventName).add(listener); - return this.off.bind(this, eventName, listener); - } - - return iterator(this, eventName); + this._getListeners(eventName).add(listener); + return this.off.bind(this, eventName, listener); } off(eventName, listener) { diff --git a/iterator.js b/iterator.js new file mode 100644 index 0000000..418cb35 --- /dev/null +++ b/iterator.js @@ -0,0 +1,37 @@ +'use strict'; +const assertEventName = require('./util'); +const EmitteryClass = require('./index'); + +async function * iterator(emitter, eventName) { + const queue = []; + const off = emitter.on(eventName, data => { + queue.push(data); + }); + + try { + /* eslint-disable no-constant-condition */ + /* eslint-disable no-await-in-loop */ + while (true) { + if (queue.length > 0) { + yield queue.shift(); + } else { + await emitter.once(eventName); + } + } + /* eslint-enable no-constant-condition */ + /* eslint-enable no-await-in-loop */ + } finally { + off(); + } +} + +module.exports = class IterableEmittery extends EmitteryClass { + on(eventName, listener) { + assertEventName(eventName); + if (typeof listener === 'function') { + this._getListeners(eventName).add(listener); + return this.off.bind(this, eventName, listener); + } + return iterator(this, eventName); + } +}; diff --git a/package.json b/package.json index 904e90e..79fa67c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "build": "babel --out-file=legacy.js index.js", "build:watch": "npm run build -- --watch", "prepublish": "npm run build", - "test": "xo && nyc ava" + "test:next": "xo && node --harmony ./node_modules/.bin/ava", + "test": "xo && nyc ava ./test/main.js ./test/legacy.js" }, "files": [ "index.js", @@ -51,7 +52,6 @@ "babel-cli": "^6.26.0", "babel-core": "^6.26.0", "babel-eslint": "^8.1.2", - "babel-plugin-transform-async-generator-functions": "^6.24.1", "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-es2015-spread": "^6.22.0", "codecov": "^3.0.0", @@ -65,17 +65,9 @@ "babel": { "plugins": [ "transform-async-to-generator", - "transform-es2015-spread", - "transform-async-generator-functions" - ], - "presets":[ - "@ava/stage-4" + "transform-es2015-spread" ] }, - "ava": { - "require": "babel-register", - "babel": "inherit" - }, "nyc": { "reporter": [ "html", diff --git a/readme.md b/readme.md index e7da2cb..059fae2 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,10 @@ emitter.emit('🦄', '🌈'); The above only works in Node.js 8 or newer. For older Node.js versions you can use `require('emittery/legacy')`. +### Node.js 9+ + +If you want the benefits of async iterators syntax your can use emittery from `require('emittery/iterator')`. Note you'll need to pass the relevant harmony flag to your nodejs process. +[see API#on for more details](#oneventname-listener) ## API @@ -47,6 +51,8 @@ Returns an unsubscribe method. Using the same listener multiple times for the same event will result in only one method call per emitted event. +##### Async iterator syntax + If you use the method with only the first argument, it will return an asynchronous iterator. Your listener will therefore be the loop body and you'll be able to unsubscribe to the event simply by breaking the loop. diff --git a/test/_run.js b/test/_run.js index 1891c05..db5c61f 100644 --- a/test/_run.js +++ b/test/_run.js @@ -37,28 +37,6 @@ module.exports = Emittery => { t.is(emitter._events.get('🦄').size, 1); }); - test('on() - async iterator', async t => { - const fixture = '🌈'; - const emitter = new Emittery(); - setTimeout(() => { - emitter.emit('🦄', fixture); - }, 300); - const iterator = emitter.on('🦄'); - const {value, done} = await iterator.next(); - t.deepEqual(done, false); - t.deepEqual(value, fixture); - }); - - test('on() - async iterator (queued)', async t => { - const fixture = '🌈'; - const emitter = new Emittery(); - const iterator = emitter.on('🦄'); - emitter.emit('🦄', fixture); - const {value, done} = await iterator.next(); - t.deepEqual(done, false); - t.deepEqual(value, fixture); - }); - test('off()', t => { const emitter = new Emittery(); const listener = () => {}; diff --git a/test/iterator.js b/test/iterator.js new file mode 100644 index 0000000..4cb7b6a --- /dev/null +++ b/test/iterator.js @@ -0,0 +1,43 @@ +import test from 'ava'; + +let Emittery; +try { + Emittery = require('../iterator'); +} catch (err) { + test('does not work due to syntax errors', t => { + t.is(err.name, 'SyntaxError'); + }); +} + +if (Emittery) { + require('./_run')(Emittery); + /* eslint-disable ava/no-async-fn-without-await */ + test('on() - async iterator (for await)', async t => { + t.plan(3); + const fixture = '🌈'; + const emitter = new Emittery(); + setInterval(() => { + emitter.emit('🦄', fixture); + }, 50); + let count = 0; + for await (const data of emitter.on('🦄')) { + count++; + if (count >= 3) { + break; + } + t.deepEqual(data, fixture); + } + }); + /* eslint-enable ava/no-async-fn-without-await */ + + test('on() - async iterator', async t => { + const fixture = '🌈'; + const emitter = new Emittery(); + const iterator = emitter.on('🦄'); + emitter.emit('🦄', fixture); + const {value, done} = await iterator.next(); + t.deepEqual(done, false); + t.deepEqual(value, fixture); + }); +} + diff --git a/util.js b/util.js new file mode 100644 index 0000000..689a409 --- /dev/null +++ b/util.js @@ -0,0 +1,6 @@ +'use strict'; +module.exports = function (eventName) { + if (typeof eventName !== 'string') { + throw new TypeError('eventName must be a string'); + } +};