[![npm version](https://badge.fury.io/js/zero.png)](http://badge.fury.io/js/roof-bus) [![Build Status](https://travis-ci.org/sskyy/roof-bus.svg?branch=master)](https://travis-ci.org/sskyy/roof-bus) roof-bus is a event bus for both practical programmers and application architects. It can be used as a simple string-based event bus whitout any buy-in in small apps. When handling large apps, especially the one with complex business logic, its powerful features like Listener Sequence Control can help you keep the system low-coupled and flexsible during the growth. ## Quick Start roof-bus is really easy to get start with, check the code below. I don't think it need any explaination. ```javascript var Bus = require('roof-bus') var bus = new Bus bus.on('start', function busListener( arg1, arg2 ){ console.log('listener fired', arg1, arg2) }) bus.fire('start', 'argument1', 'argument2') ``` Most event libraries use a simple first-in, first-out listener sequence. But sometimes simple tactic can not solve real-world problem. For example, framework usually load modules from file system in alphabetical order, but module's listener may require a diffrent registration order. Listener which control the access of certain functionality may require to be called first of the sequence. With roof-node, you can do it easily. Say that we have two listeners in different file both listen to event `start`, but one of them is in charge of validating the arguments. start-something.js: ```javascript module.exports = function(bus){ bus.on('start', function( owner ){ console.log(owner + 'started something') }) } ``` validate-arguments.js: ```javascript module.exports = function(bus){ bus.on('start',{ fn: function(owner){ if(owner !== 'Jane') throw new Error('owner is not Jane') }, first : true }) } ``` To put the validation listener to first place, simply change the listener to a object with attribute `first` that set to true. If you want to stop following listeners from being called, just throw a error. Sometime to describe complex business flow, listeners need to fire another event, like: ```javascript bus.on('process1', function(){ bus.fire('process2') }) ``` When the hierachy goes deep, it will be hard to figure out what exactly happend when top event fired. Don't worry, roof-bus generates a detailed tracestack every time. And already been used in a web framework with amazing <div style='text-align:center'> <img src='https://t.alipayobjects.com/images/rmsweb/T1WINjXddXXXXXXXXX.png' width='80%' /> </div> Read on for more usage, you may find more practical features. ## Usage ### 1. Simple `on` and `fire`. ```javascript var Bus = require('roof-bus') var bus = new Bus bus.on('start', function busListener( arg1, arg2 ){ console.log('listener fired', arg1, arg2) }) bus.fire('start', 'argument1', 'argument2') ``` ### 2. Listener Sequence Control Order of listeners on the same event can be controlled. Just name you listener function and then use the function name in attribute `before` or `after`. ```javascript bus.on('start', function listener1( arg1, arg2 ){ console.log('listener1 fired', arg1, arg2) }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log('listener2 fired', arg1, arg2) }, before : ['listener1'] }) bus.fire('start', 'argument1', 'argument2') ``` There are four order control attributes: `before` `after` `first` `last`. Check below. ```javascript bus.on('start', function listener1( arg1, arg2 ){ console.log('listener1 fired', arg1, arg2) }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log('listener2 fired', arg1, arg2) }, before : ['listener1'] }) bus.on('start', { fn: function listener3( arg1, arg2 ){ console.log('listener3 fired', arg1, arg2) }, after : ['listener1'] }) bus.on('start', { fn: function listener4( arg1, arg2 ){ console.log('listener4 fired', arg1, arg2) }, first : true }) bus.fire('start', 'argument1', 'argument2') //fire order: 4 2 1 3 ``` ### 3. Handle asynchronicity `fire` method always return a promise. If you have synchronous code in listener and want roof-bus wait for you, return a promise. ```javascript bus.on('start', function listener1( arg1, arg2 ){ return new Promise(function(resolve,reject){ setTimeout(resolve, 1000) }) }) bus.fire('start', 'argument1', 'argument2').then(function(){ console.log('show in 1 second') }) ``` **Note** that listeners are fired synchronously as default. So listener which returns promise will block followers until promise resolve. ```javascript bus.on('start', function listener1( arg1, arg2 ){ return new Promise(function(resolve,reject){ setTimeout(resolve, 1000) }) }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log('will wait for listener1 resolve') }, after : ['listener1'] }) bus.fire('start', 'argument1', 'argument2').then(function(){ console.log('will wait all listener fired') }) ``` If you want some listeners to execute asynchronously, you can set `async` attribute to `true` as below. ```javascript bus.on('start', { fn: function listener1( arg1, arg2 ){ return new Promise(function(resolve,reject){ setTimeout(resolve, 1000) }) }, async : true }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log('will not be blocked by listener1') }, after : ['listener1'] }) bus.fire('start', 'argument1', 'argument2').then(function(){ console.log('show in 1 second') }) ``` ### 4. Generator support If you have asynchrous code and do not like promise, you can use generator: ```javascript bus.on('start', function *listener1(){ yield somethingAsync() }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log('will be blocked by listener1') }, after : ['listener1'] }) ``` ### 5. Passing data between listeners Note the order of listeners is important when passing data. ```javascript bus.on('start', function listener1( arg1, arg2 ){ this.data.set('name','Bill') }) bus.on('start', { fn: function listener2( arg1, arg2 ){ console.log(this.data.get('name')) // 'Bill' }, after : 'listener1' }) ``` ### 6. Fire inside listener You already we can fire another event inside listener, and roof-bus will keep a tracestack for you. Just keep one thing in mind that you must use `this.fire` inside the listener: ```javascript bus.on('topEvent', function(){ this.fire('rootEvent') }) ``` ### 7. Error handling You can throw a build-in Error instance or use roof-bus `error` method. ```javascript bus.on('start', function listener1( arg1, arg2 ){ throw new Error('some error') //or //return this.error(500,{msg:'some error'}) }) bus.fire('start').then(function(){ }).catch(function( error){ console.log( error) }) ``` ## Advanced ### List events It easy to get all registered events: ```javascript bus.on('sing', function(){}) bus.on('dance', function(){}) var events = bus.getEvents() console.log( events ) //'sing' 'dance' ``` ### List listeners Get all listeners for listened on certain event: ```javascript function listener1(){} function listener2(){} bus.on(event, listener1) bus.on(event, { fn: listener2, before : 'listener1' }) var listeners = bus.getListeners(event).toArray() assert.equal( listeners[0].fn, listener2) assert.equal( listeners[0].event, event) assert.equal( listeners[1].fn, listener1) ``` ### Get listener tracestack ```javascript bus.on('dance', function danceListener(){ this.data.set('name','Jane') }) bus.fire('dance') console.log( bus._runtime.data ) ``` The tracestack structure: ```javascript { "event": { "name": "dance", "arguments": [] }, "listeners": { "danceListener": { "fn" : [Function firstListener], "event" : "dance", "data" :{ "name":"Jane" } } } } ``` Browse the test cases and examples for more detail. More documents coming soon.