├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── parallel.js └── seq.js ├── index.js ├── lib └── event_patch.js ├── package.json └── test.js /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyrchen/node-eventasync/c05597f131f1893a440b7357ef357f6c86a2cda0/.eslintignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // Use this file as a starting point for your project's .eslintrc. 2 | // Copy this file, and add rule overrides as needed. 3 | { 4 | "extends": "airbnb/base", 5 | "rules": { 6 | "new-cap": 1, 7 | "no-param-reassign": 0, 8 | "max-statements": [2, 50], 9 | "max-depth": [2, 5], 10 | "max-nested-callbacks": [2, 3], 11 | "max-params": [2, 5], 12 | "complexity": [2, 10], 13 | "max-len": [2, 120, 4] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tyr Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-eventasync 2 | 3 | node.js event emitter monkey patch for supporting asynchronous listeners. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install eventasync 9 | ``` 10 | 11 | ## Usage 12 | 13 | To use eventasync, just require it like this: 14 | 15 | ```js 16 | const EventEmitter = require('events').EventEmitter; 17 | require('eventasync'); 18 | 19 | const ev = new EventEmitter(); 20 | 21 | ev.emitAsync('event1'); 22 | ev.emitAsyncSeq('event2'); 23 | ``` 24 | 25 | ``emitAsync`` will call the registered listeners in parallel. ``emitAsyncSeq`` will call the registered listeners in sequence. Both of them will return a ``Promise`` so that caller function can evaluate the result (or error) by using ``Promise`` interface. 26 | 27 | For event listeners, if it is an async operation, you need to return a ``Promise`` for that async operation. 28 | 29 | ``emitAsync`` uses ``Rx.Observable.mergeAll()``, and ``emitAsyncSeq`` uses ``Rx.Observable.concatAll()``. 30 | 31 | ## Examples 32 | 33 | Here's an example for running all listeners in sequence: 34 | 35 | ```js 36 | const EventEmitter = require('events').EventEmitter; 37 | require('eventasync'); 38 | 39 | const ev = new EventEmitter(); 40 | ev.data = []; 41 | 42 | const listener1 = data => { 43 | return new Promise(res => { 44 | setTimeout(() => { 45 | data.push(1); 46 | res(true); 47 | }, 400); 48 | }); 49 | } 50 | 51 | const listener2 = data => data.push(2); 52 | 53 | const listener3 = data => { 54 | return new Promise(res => { 55 | setTimeout(() => { 56 | data.push(3); 57 | res(true); 58 | }, 200); 59 | }); 60 | } 61 | 62 | ev.on('event', listener1); 63 | ev.on('event', listener2); 64 | ev.on('event', listener3); 65 | 66 | const promise = ev.emitAsyncSeq('event', ev.data); 67 | promise.then(() => { 68 | ev.data.push(4); 69 | console.log('data:', ev.data); 70 | }); 71 | // data: [ 1, 2, 3, 4 ] 72 | ``` 73 | 74 | Here an example for running all listeners in parallel, while one of them throw an error: 75 | 76 | ```js 77 | const EventEmitter = require('events').EventEmitter; 78 | require('eventasync'); 79 | 80 | const ev = new EventEmitter(); 81 | ev.data = []; 82 | 83 | const listener1 = data => { 84 | return new Promise((res, rej) => { 85 | setTimeout(() => { 86 | rej('Something is wrong!'); 87 | }, 400); 88 | }); 89 | } 90 | 91 | const listener2 = data => { 92 | return new Promise(res => { 93 | setTimeout(() => { 94 | data.push(2); 95 | res(true); 96 | }, 200); 97 | }); 98 | } 99 | 100 | const listener3 = data => data.push(3); 101 | 102 | ev.on('event', listener1); 103 | ev.on('event', listener2); 104 | ev.on('event', listener3); 105 | 106 | const promise = ev.emitAsync('event', ev.data); 107 | promise.then(() => { 108 | ev.data.push(4); 109 | console.log('data:', ev.data); 110 | }).catch(err => { 111 | console.log(err, ev.data); 112 | }); 113 | // Something is wrong! [ 3, 2 ] 114 | ``` 115 | 116 | ## License 117 | 118 | MIT. See [LICENSE](./LICENSE). -------------------------------------------------------------------------------- /examples/parallel.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | require('./../index'); 3 | 4 | const ev = new EventEmitter(); 5 | ev.data = []; 6 | 7 | const listener1 = () => 8 | new Promise((res, rej) => 9 | setTimeout(() => { 10 | rej('Something is wrong!'); 11 | }, 400) 12 | ); 13 | 14 | const listener2 = data => 15 | new Promise(res => 16 | setTimeout(() => { 17 | data.push(2); 18 | res(true); 19 | }, 200) 20 | ); 21 | 22 | const listener3 = data => data.push(3); 23 | 24 | ev.on('event', listener1); 25 | ev.on('event', listener2); 26 | ev.on('event', listener3); 27 | 28 | const promise = ev.emitAsync('event', ev.data); 29 | promise.then(() => { 30 | ev.data.push(4); 31 | console.log('data:', ev.data); 32 | }).catch(err => { 33 | console.log(err, ev.data); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/seq.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events').EventEmitter; 2 | require('./../index'); 3 | 4 | const ev = new EventEmitter(); 5 | ev.data = []; 6 | 7 | const listener1 = data => 8 | new Promise(res => 9 | setTimeout(() => { 10 | data.push(1); 11 | res(true); 12 | }, 400) 13 | ); 14 | 15 | const listener2 = data => data.push(2); 16 | 17 | const listener3 = data => 18 | new Promise(res => 19 | setTimeout(() => { 20 | data.push(3); 21 | res(true); 22 | }, 200) 23 | ); 24 | 25 | ev.on('event', listener1); 26 | ev.on('event', listener2); 27 | ev.on('event', listener3); 28 | 29 | const promise = ev.emitAsyncSeq('event', ev.data); 30 | promise.then(() => { 31 | ev.data.push(4); 32 | console.log('data:', ev.data); 33 | }).catch(err => { 34 | console.log(err); 35 | }); 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./lib/event_patch'); 2 | -------------------------------------------------------------------------------- /lib/event_patch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // this code is derived from original event.js of nodejs. 4 | 5 | const EventEmitter = require('events').EventEmitter; 6 | const Rx = require('rx'); 7 | 8 | /** 9 | * Handle error event, if there is no 'error' event listener then throw. 10 | * 11 | * @param domain 12 | * @returns {boolean} 13 | * @private 14 | */ 15 | function handleError(domain) { 16 | let er = arguments[1]; 17 | if (domain) { 18 | if (!er) er = new Error('Uncaught, unspecified "error" event.'); 19 | er.domainEmitter = this; 20 | er.domain = domain; 21 | er.domainThrown = false; 22 | domain.emit('error', er); 23 | } else if (er instanceof Error) { 24 | throw er; // Unhandled 'error' event 25 | } else { 26 | // At least give some kind of context to the user 27 | const err = new Error(`Uncaught, unspecified "error" event. (${er})`); 28 | err.context = er; 29 | throw err; 30 | } 31 | return false; 32 | } 33 | 34 | /** 35 | * generate event async function for sequential and parallel use cases 36 | * 37 | * @param sequential {boolean} true for sequential execution of listener. Default is false. 38 | * @returns {function} 39 | */ 40 | function emitAsyncGen(sequential) { 41 | /** 42 | * Monkey patch on event emitter so that it can work with async handler 43 | * 44 | * @param type {string} event type, used for object.on(''). 45 | * @returns {Promise} 46 | */ 47 | return function emitAsync(type) { 48 | const self = this; 49 | return new Promise((res, rej) => { 50 | let needDomainExit = false; 51 | let doError = (type === 'error'); 52 | 53 | const events = self._events; 54 | if (events) doError = (doError && events.error === null); 55 | else if (!doError) return res(false); 56 | 57 | const domain = self.domain; 58 | 59 | try { 60 | if (doError) return res(handleError(domain)); 61 | } catch (e) { 62 | rej(e); 63 | } 64 | 65 | let handlers = events[type]; 66 | 67 | if (!handlers) return res(false); 68 | 69 | if (domain && self !== process) { 70 | domain.enter(); 71 | needDomainExit = true; 72 | } 73 | 74 | const isFn = typeof handlers === 'function'; 75 | const args = Array.prototype.slice.call(arguments, 1); 76 | 77 | if (isFn) handlers = [handlers]; 78 | const mode = sequential ? 'concatAll' : 'mergeAll'; 79 | Rx.Observable.from(handlers) 80 | .map(handler => 81 | new Rx.Observable.create(observer => { 82 | let result; 83 | try { 84 | result = handler.apply(self, args); 85 | } catch (e) { 86 | result = new Promise((_res, _rej) => _rej(e)); 87 | } 88 | 89 | if (result instanceof Promise) { 90 | result.then(data => { 91 | observer.onNext(data); 92 | observer.onCompleted(); 93 | }).catch(err => observer.onError(err)); 94 | } else { 95 | observer.onNext(true); 96 | observer.onCompleted(); 97 | } 98 | }) 99 | )[mode]() 100 | .toArray() 101 | .subscribe( 102 | () => null, 103 | err => rej(err), 104 | () => { 105 | if (needDomainExit) domain.exit(); 106 | res(true); 107 | } 108 | ); 109 | }); 110 | }; 111 | } 112 | 113 | EventEmitter.prototype.emitAsync = emitAsyncGen(false); 114 | EventEmitter.prototype.emitAsyncSeq = emitAsyncGen(true); 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventasync", 3 | "version": "1.0.0", 4 | "description": "node.js event emitter monkey patch for supporting asynchronous listeners", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava -v test.js", 8 | "lint": "eslint ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/tyrchen/node-eventasync.git" 13 | }, 14 | "keywords": [ 15 | "event", 16 | "eventEmitter", 17 | "async", 18 | "reactive" 19 | ], 20 | "author": "Tyr Chen", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/tyrchen/node-eventasync/issues" 24 | }, 25 | "homepage": "https://github.com/tyrchen/node-eventasync#readme", 26 | "dependencies": { 27 | "rx": "^4.0.8" 28 | }, 29 | "devDependencies": { 30 | "ava": "^0.12.0", 31 | "eslint": "^1.10", 32 | "eslint-config-airbnb": "^5.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter; 4 | require('./index'); 5 | 6 | const test = require('ava').test; 7 | 8 | class Server extends EventEmitter { 9 | constructor() { 10 | super(); 11 | this.data = []; 12 | } 13 | } 14 | 15 | const listener1 = data => 16 | new Promise(res => 17 | setTimeout(() => { 18 | data.push(1); 19 | res(true); 20 | }, 400) 21 | ); 22 | 23 | const listener2 = data => data.push(2); 24 | 25 | const listener3 = data => 26 | new Promise(res => 27 | setTimeout(() => { 28 | data.push(3); 29 | res(true); 30 | }, 200) 31 | ); 32 | 33 | test('emit() on async listener works on sync listeners only', t => { 34 | const server = new Server(); 35 | server.on('event', listener1); 36 | server.on('event', listener2); 37 | server.on('event', listener3); 38 | server.emit('event', server.data); 39 | server.data.push(4); 40 | t.same(server.data, [2, 4]); 41 | }); 42 | 43 | test('emitAsync() run listeners in parallel', t => { 44 | const server = new Server(); 45 | server.on('event', listener1); 46 | server.on('event', listener2); 47 | server.on('event', listener3); 48 | const promise = server.emitAsync('event', server.data); 49 | 50 | promise.then(() => { 51 | server.data.push(4); 52 | t.same(server.data, [2, 3, 1, 4]); 53 | }); 54 | }); 55 | 56 | test('emitAsyncSeq() run listeners in sequential', t => { 57 | const server = new Server(); 58 | server.on('event', listener1); 59 | server.on('event', listener2); 60 | server.on('event', listener3); 61 | const promise = server.emitAsyncSeq('event', server.data); 62 | 63 | promise.then(() => { 64 | server.data.push(4); 65 | t.same(server.data, [1, 2, 3, 4]); 66 | }); 67 | }); 68 | --------------------------------------------------------------------------------