Eventorjs
promise based event emitter (pub/sub, message bus) on steroids (cascade waterfall,middlewares,namespaces,wildcards) (nodejs and browser)
Install / Use
/learn @neuronetio/EventorjsREADME
eventorjs
async event emitter on steroids with
- emit (
Promise.all) - cascade (waterfall = output of one listener is passed as input for the next one),
- middlewares (useBefore, useAfter and useBeforeAll,useAfterAll)
- before and after events to easly create events before some action and after it
- event namespaces (event grouping,removing & executing specified group only)
- wildcards & express-like path params & regexp ("user.*" = user.created user.destroyed, "/user/:id/created")
- timeouts
- prepend
eventorjs was build for loosely coupled inter-module communication and for hooks system, but can be used for other purposes as well, just like normal event emitter with extra features.
Eventor was created to make your code more reusable. Eventor gives you super easy way of build apps/modules that are easly extendable with no need to change original code. You can write a module, and then write another module that will extend (loosely) the first one, without touching the code of the first one. You will have two modules that you can copy elsewhere and copy only those functions (submodules) that you need. Eventor should be used as hooks that can be used to change behaviour of the module without changing the original code. Eventor is also good way for creating micro-modules or functions which will be responsible for only one thing. Instead of building large customized module with different jobs running here and there - you can create one module for general use case and a lot of micro-modules that will extend (loosely) the main one. You will have ability to copy and paste or install those micro-modules in other projects. You will have a lot of benefits if you compose your system that way. With eventor your system will be more composeable.
For example you have built a nice user module, that you can copy and paste or install in other projects. Some day your client wants to add some custom feature to the user module. If you modify this module you loose ability to copy and paste it in other projects or to update it. With eventor you can save original module and use eventor to add new feature in other specialized micro-module that will be listening "user.create" event (for example) and add custom data before saving it to database (for example).
With eventor you can create micro modules - specialized modules that do only one thing and do it well.
All modules and submodules will be loosely coupled - if nobody listen, nothing will happen - you don't need to require the user module to extend it.
nodejs usage
npm install --save eventorjs
const Eventor = require("eventorjs");
const eventor = Eventor();
// or just
const eventor = require("eventorjs")();
browser usage
<script src="http://yourwebsite/js/eventor.min.js"></script>
const eventor = Eventor();
emit
let eventor = Eventor();
function doSomething(data,event){
return new Promise((resolve,reject)=>{
resolve("test1");
});
}
eventor.on("test",doSomething);
// you can use promises as return value but it is not necessary
let event2id = eventor.on("test",(data,event)=>{
return "test2";
});
eventor.emit("test",{someData:"someValue"}).then((results)=>{
console.log(results); // -> ["test1","test2"]
});
let testEventListeners = eventor.listeners("test");
eventor.off(doSomething); // function
eventor.off(event2id); // or listener id
cascade
Cascade is when output of one listener is passed as input to the next one.
let eventor = new Eventor();
eventor.on("test",(data,event)=>{
return new Promise((resolve,reject)=>{
resolve(data+2);
});
});
eventor.on("test",(data,event)=>{
return new Promise((resolve,reject)=>{
resolve(data+3);
});
});
eventor.cascade("test",5)
.then((result)=>{
console.log(result); // -> 10
});
Cascade is a sequence
emit run on listeners simultaneously but cascade is waiting for each listener to finish - to go further.
So when you have ten on listeners which need 1 second to do their job,
when you emit an event, the total work time will be just one second,
but when you cascade an event, the total time will be 10 seconds so be aware of it
promises
Eventor is based on promises. You can choose your A+ implementation of promises like bluebird. We are recommending bluebird, because it is the fastest one, and have a lot of features. If you need native Promise in your project just do nothing.
const bluebird = require("bluebird");
let eventor = Eventor({promise:bluebird});
or
const Promise = require("bluebird");
let eventor = Eventor({promise:Promise});
namespace
let eventor = new Eventor();
eventor.on("module1","test",(data,event)=>{
return new Promise((resolve,reject)=>{
resolve(data+"-module1");
});
});
eventor.on("module2","test",(data,event)=>{
return new Promise((resolve,reject)=>{
resolve(data+"-module2");
});
});
eventor.cascade("module1","test","someData")
.then((result)=>{
console.log(result); // -> "someData-module1"
});
eventor.cascade("module2","test","someData")
.then((result)=>{
console.log(result); // -> "someData-module2"
});
eventor.emit("module2","test","someData")
.then((results)=>{
console.log(results); // -> ["someData-module2"]
});
let module1Listeners = eventor.getListenersFromNamespace("module1");
//or
let module1TestListeners = eventor.listeners("module1","test");
let module2Listeners = eventor.getListenersFromNamespace("module2");
//or
let module2TestListeners = eventor.listeners("module2","test");
eventor.removeListenersFromNamespace("module1");
Middlewares (useBefore, useAfter & useBeforeAll, useAfterAll)
Middlewares are fired before or after normal on listeners.
They can modify input before passing it to the listeners and output before result is returned to emitter.
The can be used for other things as well (for example prepare or remove something before and after some job).
"image is worth a thousand words"
middleware diagram
EMIT:

CASCADE:

middleware example
For example we can prepare some data before normal event is fired like db connection. (It is not really cool way to play with db connections, but for demonstration purpose we can do this)
let eventor = new Eventor();
eventor.useBeforeAll("doSomething",(data,event)=>{
return new Promise((resolve,reject)=>{
let db = connectToTheDatabase();
data.db = db;
resolve(data);
});
});
eventor.on("doSomething",(data,event)=>{
return new Promise((resolve,reject)=>{
data.result = data.db("read from database");
resolve(data);
});
});
eventor.useAfterAll("doSomething",(data,event)=>{
return new Promise((resolve,reject)=>{
delete data.db;
resolve(data);
});
});
eventor.cascade("doSomething",{}).then((result)=>{
console.log(result); // -> {result:databaseResult} without db connection
});
IMPORTANT useAfterAll will have an array of results in emit mode and just result (value) in cascade mode.
To figure out blindly in wich mode you are you can use event.type parameter.
eventor.useAfterAll("doSomething",(data,event)=>{
if(event.type=="emit"){ // in emit mode input is an array
data=data.map((item,index)=>{
return "test "+index;
}); // result of the emit process will be ["test 1","test 2","test 3",...]
}else if(event.type=="cascade"){ // in cascade mode input is just value
return "test";
}// result of the cascade process will be just "test"
});
timeouts
Default timeout is 60sec. but if you want other timetout then pass it to the options.
When timeout happened the timeout event is emited but no other action will be taken.
let eventor = Eventor({timeout:500}); //500ms timeout
eventor.on("timeout",(data,event)=>{
// do something with timeout
console.log(data.arguments); // arguments from cascade or emit -> ["test","testData"]
console.log(data.type); // -> "cascade"
console.log(data.error); // -> instance of new Error("timeout"); to track source code
});
eventor.on("test",(data,event)=>{
return new Promise((resolve)=>{
setTimeout(()=>{
resolve("yeahhh");
},900);// more than 500 = timeout
});
});
eventor.cascade("test","testData").then((result)=>{
console.log(result); // -> yeahhh
}).catch((e)=>{
// there will be no error at all - only 'timeout' event
});
prepend
If you want to prepend listener to the beginning just add another argument 0 at the end of argument list. If you have multiple listeners that was prepended, then later declared will be the first one.
let eventor=Eventor();
let order = [];
eventor.on("test",(data,event)=>{
order.push("first");
});
eventor.on("test",(data,event)=>{
order.push("second");
},0); // <- 0 here
eventor.on("test",(data,event)=>{
order.push("third");
},0); // <- 0 here
eventor.cascade("test","data").then(()=>{
console.log(order); // -> ["third","second","first"]
});
let eventor=Eventor();
let order = [];
eventor.on("namespace","test",(data,event)=>{
order.push("first");
});
eventor.on("namespace","test",(data,event)=>{
order.push("second");
},0); // <- 0 add here
eventor.on("namespace","test",(data,event)=>{
order.push("third");
});
eventor.on("namespace","test",(data,event)=>{
order.push("fourth");
},0); // <- 0 add here
eventor.cascade("namespace","test","data").then(()=>{
console.log(order); // -> ["fourth","second","first","third"]
});
Eventor.before & Eventor.after
There are often situations that you need to emit something and get results from listeners before some action (for example db.write).
For this purpose you have built in Eventor.before emitter so you don't need to make ugly event
