├── .gitignore ├── Rust ├── .gitignore ├── rustfmt.toml ├── src │ ├── lib.rs │ ├── event.rs │ └── event_emitter.rs ├── Cargo.toml ├── examples │ ├── example1.rs │ ├── example2.rs │ ├── example4.rs │ └── example3.rs ├── tests │ ├── event.rs │ └── event_emitter.rs └── README.md ├── Haskell ├── .gitignore ├── 2-usage.hs ├── 1-simple.hs ├── 3-enhanced.hs └── 4-usage.hs ├── eslint.config.js ├── README.md ├── prettier.config.js ├── JavaScript ├── 2-usage.js ├── 3-enhanced.js ├── 7-closure.js ├── 5-usage.js ├── 4-star-fix.js ├── 1-simple.js ├── 6-closure.js ├── 9-min.js ├── 8-methods.js ├── b-class.js ├── a-prod.js ├── c-async.js └── tasks.txt ├── .editorconfig ├── Go ├── 1-simple_test.go ├── 1-simple.go ├── 2-enhanced_test.go └── 2-enhanced.go ├── package.json ├── Java ├── 2-usage.java └── 1-simple.java ├── CSharp ├── SimpleEventFlow.cs ├── ConsoleEventsTest.cs └── JsStyleEventEmitter.cs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Haskell/.gitignore: -------------------------------------------------------------------------------- 1 | *.hi 2 | *.o 3 | 2-usage 4 | 4-usage 5 | -------------------------------------------------------------------------------- /Rust/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | take_source_hints = true 3 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const init = require('eslint-config-metarhia'); 4 | 5 | module.exports = init; 6 | -------------------------------------------------------------------------------- /Rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod event; 2 | mod event_emitter; 3 | 4 | pub use event::Event; 5 | pub use event_emitter::EventEmitter; 6 | -------------------------------------------------------------------------------- /Rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "event-emitter" 3 | version = "0.1.0" 4 | authors = ["Alexey Orlenko "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## EventEmitter Examples 2 | 3 | [![Таймеры, таймауты, EventEmitter](https://img.youtube.com/vi/LK2jveAnRNg/0.jpg)](https://www.youtube.com/watch?v=LK2jveAnRNg) 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | printWidth: 80, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | }; 11 | -------------------------------------------------------------------------------- /Haskell/2-usage.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import EventEmitter 4 | 5 | data Struct = Struct { a :: Int } deriving (Show) 6 | 7 | application = newEventEmitter 8 | 9 | main = mconcat . emit "smth" (Struct 5) . on "smth" print $ application 10 | -------------------------------------------------------------------------------- /JavaScript/2-usage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('./1-simple.js'); 4 | 5 | const ee = new EventEmitter(); 6 | 7 | ee.on('event1', (data) => { 8 | console.dir(data); 9 | }); 10 | 11 | ee.emit('event1', { a: 5 }); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /Go/1-simple_test.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | import "fmt" 4 | 5 | func ExampleEventEmitter() { 6 | application := make(EventEmitter) 7 | 8 | application.On("smth", func(data ...interface{}) { 9 | fmt.Println(data...) 10 | }) 11 | 12 | application.Emit("smth", struct{ A int }{A: 5}) 13 | 14 | // Output: {5} 15 | } 16 | -------------------------------------------------------------------------------- /JavaScript/3-enhanced.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const events = require('node:events'); 4 | 5 | const emitter = () => { 6 | const ee = new events.EventEmitter(); 7 | const emit = ee.emit; 8 | ee.emit = (...args) => { 9 | emit.apply(ee, args); 10 | args.unshift('*'); 11 | emit.apply(ee, args); 12 | }; 13 | return ee; 14 | }; 15 | 16 | module.exports = emitter; 17 | -------------------------------------------------------------------------------- /Go/1-simple.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | type Listener = func(...interface{}) 4 | type EventEmitter map[string][]Listener 5 | 6 | func (ee EventEmitter) On(name string, listener Listener) { 7 | ee[name] = append(ee[name], listener) 8 | } 9 | 10 | func (ee EventEmitter) Emit(name string, data ...interface{}) { 11 | for _, listener := range ee[name] { 12 | listener(data...) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /JavaScript/7-closure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = (events = {}) => ({ 4 | on: (name, fn) => (events[name] = events[name] || []).push(fn), 5 | emit: (name, ...data) => (events[name] || []).forEach((fn) => fn(...data)), 6 | }); 7 | 8 | // Usage 9 | 10 | const ee = emitter(); 11 | 12 | ee.on('event1', (data) => { 13 | console.dir(data); 14 | }); 15 | 16 | ee.emit('event1', { a: 5 }); 17 | -------------------------------------------------------------------------------- /JavaScript/5-usage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = require('./4-star-fix.js'); 4 | 5 | const ee = emitter(); 6 | 7 | ee.on('event1', (data) => { 8 | console.log('Certain event'); 9 | console.dir(data); 10 | }); 11 | 12 | ee.on('*', (name, data) => { 13 | console.log('Any event'); 14 | console.dir([name, data]); 15 | }); 16 | 17 | ee.emit('event1', { a: 5 }); 18 | ee.emit('event2', { a: 500 }); 19 | ee.emit('*', { a: 700 }); 20 | -------------------------------------------------------------------------------- /Haskell/1-simple.hs: -------------------------------------------------------------------------------- 1 | module EventEmitter where 2 | 3 | import Data.Map.Strict as M 4 | import Data.Maybe 5 | 6 | type EventEmitter a f = Map a [f] 7 | 8 | on :: Ord a => a -> f -> EventEmitter a f -> EventEmitter a f 9 | on name fn = alter (Just . maybe [fn] (fn:)) name 10 | 11 | emit :: Ord a => a -> b -> EventEmitter a (b -> c) -> [c] 12 | emit name dat = fmap ($ dat) . fromMaybe [] . M.lookup name 13 | 14 | newEventEmitter :: EventEmitter a f 15 | newEventEmitter = empty 16 | -------------------------------------------------------------------------------- /JavaScript/4-star-fix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const events = require('node:events'); 4 | 5 | const emitter = () => { 6 | const ee = new events.EventEmitter(); 7 | const emit = ee.emit; 8 | ee.emit = (...args) => { 9 | const [name] = args; 10 | if (name === '*') throw new Error('Event name "*" is reserved'); 11 | emit.apply(ee, args); 12 | args.unshift('*'); 13 | emit.apply(ee, args); 14 | }; 15 | return ee; 16 | }; 17 | 18 | module.exports = emitter; 19 | -------------------------------------------------------------------------------- /JavaScript/1-simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = function () { 4 | this.events = {}; // hash of array of function 5 | }; 6 | 7 | EventEmitter.prototype.on = function (name, fn) { 8 | const event = this.events[name]; 9 | if (event) event.push(fn); 10 | else this.events[name] = [fn]; 11 | }; 12 | 13 | EventEmitter.prototype.emit = function (name, ...data) { 14 | const event = this.events[name]; 15 | if (!event) return; 16 | for (const listener of event) listener(...data); 17 | }; 18 | 19 | module.exports = EventEmitter; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "author": "Timur Shemsedinov ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "eslint ./Exercises; hpw", 9 | "ci": "eslint ./Exercises && hpw", 10 | "lint": "eslint . && prettier -c \"**/*.js\"", 11 | "fix": "eslint . --fix && prettier --write \"**/*.js\"" 12 | }, 13 | "dependencies": { 14 | "hpw": "^0.2.4", 15 | "eslint": "^9.12.0", 16 | "eslint-config-metarhia": "^9.1.1", 17 | "prettier": "^3.3.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /JavaScript/6-closure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = () => { 4 | const events = {}; 5 | return { 6 | on: (name, fn) => { 7 | const event = events[name]; 8 | if (event) event.push(fn); 9 | else events[name] = [fn]; 10 | }, 11 | emit: (name, ...data) => { 12 | const event = events[name]; 13 | if (event) event.forEach((fn) => fn(...data)); 14 | }, 15 | }; 16 | }; 17 | 18 | // Usage 19 | 20 | const ee = emitter(); 21 | 22 | ee.on('event1', (data) => { 23 | console.dir(data); 24 | }); 25 | 26 | ee.emit('event1', { a: 5 }); 27 | -------------------------------------------------------------------------------- /Haskell/3-enhanced.hs: -------------------------------------------------------------------------------- 1 | module EnhancedEmitter where 2 | 3 | import Data.Map.Strict as M 4 | import Control.Monad (when) 5 | import EventEmitter as EE 6 | 7 | on :: Ord a => a -> f -> EventEmitter a (b -> f) -> EventEmitter a (b -> f) 8 | on name fn = EE.on name $ const fn 9 | 10 | onAny :: f -> EventEmitter String f -> EventEmitter String f 11 | onAny fn = EE.on "*" fn 12 | 13 | emit :: String -> b -> EventEmitter String (String -> b -> c) -> [c] 14 | emit name dat ee = ($ dat) <$> named ++ any 15 | where named = if name /= "*" then EE.emit name name ee else [] 16 | any = EE.emit "*" name ee 17 | 18 | emitter = EE.newEventEmitter 19 | -------------------------------------------------------------------------------- /Haskell/4-usage.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import EnhancedEmitter 4 | 5 | data Struct = Struct { a :: Int } deriving (Show) 6 | 7 | application = emitter 8 | application' = onAny printAny . on "smth" printCertain $ application 9 | 10 | main = mconcat . concat . map ($ application') $ 11 | [ emit "smth" (Struct 5) 12 | , emit "smth2" (Struct 500) 13 | , emit "*" (Struct 700) 14 | ] 15 | 16 | printCertain :: Show a => a -> IO () 17 | printCertain dat = do 18 | putStrLn "Certain event" 19 | print dat 20 | 21 | printAny :: Show a => String -> a -> IO () 22 | printAny name dat = do 23 | putStrLn "Any event" 24 | print (name, dat) 25 | -------------------------------------------------------------------------------- /Java/2-usage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of using SimpleEventEmitter. 3 | */ 4 | final class SimpleEventEmitterUsage { 5 | /** 6 | * Private constructor prevents class from being instantiated. 7 | */ 8 | private SimpleEventEmitterUsage() { 9 | 10 | } 11 | 12 | /** 13 | * Entry point of the program. 14 | * 15 | * @param args command-line arguments, not used 16 | */ 17 | public static void main(final String[] args) { 18 | SimpleEventEmitter eventEmitter = new SimpleEventEmitter<>(); 19 | eventEmitter.on("something", System.out::println); 20 | eventEmitter.emit("something", "5"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Go/2-enhanced_test.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | import "fmt" 4 | 5 | func ExampleEnhancedEmitter() { 6 | application := NewEnhancedEmitter() 7 | 8 | application.On("smth", func(data ...interface{}) { 9 | fmt.Println("Certain event") 10 | fmt.Println(data...) 11 | }) 12 | 13 | application.OnAny(func(name string, data ...interface{}) { 14 | fmt.Println("Any event") 15 | fmt.Print(name + " ") 16 | fmt.Println(data...) 17 | }) 18 | 19 | application.Emit("smth", struct{ A int }{5}) 20 | application.Emit("smth2", struct{ A int }{500}) 21 | application.Emit("*", struct{ A int }{5}) 22 | // Output: 23 | // Certain event 24 | // {5} 25 | // Any event 26 | // smth {5} 27 | // Any event 28 | // smth2 {500} 29 | // Any event 30 | // * {5} 31 | } 32 | -------------------------------------------------------------------------------- /Go/2-enhanced.go: -------------------------------------------------------------------------------- 1 | package emitter 2 | 3 | type AnyListener func(string, ...interface{}) 4 | 5 | type Emitter interface { 6 | On(name string, listener Listener) 7 | Emit(name string, data ...interface{}) 8 | } 9 | 10 | type EnhancedEmitter struct { 11 | EventEmitter 12 | anyListeners []AnyListener 13 | } 14 | 15 | func (eee EnhancedEmitter) Emit(name string, data ...interface{}) { 16 | ee := eee.EventEmitter 17 | if name != "*" { 18 | ee.Emit(name, data...) 19 | } 20 | for _, listener := range eee.anyListeners { 21 | listener(name, data...) 22 | } 23 | } 24 | 25 | func (eee *EnhancedEmitter) OnAny(listener AnyListener) { 26 | eee.anyListeners = append(eee.anyListeners, listener) 27 | } 28 | 29 | func NewEnhancedEmitter() *EnhancedEmitter { 30 | ee := make(EventEmitter) 31 | return &EnhancedEmitter{ 32 | ee, 33 | []AnyListener{}, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Rust/examples/example1.rs: -------------------------------------------------------------------------------- 1 | // Example 1: an EventEmitter with events of type `GreetEvent` and payload of 2 | // type `&str`. 3 | 4 | extern crate event_emitter; 5 | 6 | use event_emitter::EventEmitter; 7 | 8 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] 9 | enum GreetEvent { 10 | SayHello, 11 | SayBye, 12 | } 13 | 14 | fn main() { 15 | let mut emitter = EventEmitter::new(); 16 | 17 | emitter.on(GreetEvent::SayHello, |name| { 18 | println!("Hello, {}!", name); 19 | }); 20 | 21 | emitter.on(GreetEvent::SayHello, |name| { 22 | println!("Someone said hello to {}.", name); 23 | }); 24 | 25 | emitter.on(GreetEvent::SayBye, |name| { 26 | println!("Bye, {}, hope to see you again!", name); 27 | }); 28 | 29 | emitter.emit(GreetEvent::SayHello, "Alex"); 30 | emitter.emit(GreetEvent::SayBye, "Alex"); 31 | } 32 | -------------------------------------------------------------------------------- /CSharp/SimpleEventFlow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HowProgrammingWorks.CSharpEvents 8 | { 9 | public class SimpleEventFlow 10 | { 11 | private event EventHandler SimpleEvent; 12 | 13 | public void Subscribe() 14 | { 15 | SimpleEvent += DoWork; 16 | } 17 | 18 | public void Unsubscribe() 19 | { 20 | SimpleEvent -= DoWork; 21 | } 22 | 23 | public void Invoke() 24 | { 25 | //I don't use null-conditional Operator ('?') to show events behaviour 26 | SimpleEvent.Invoke(this,new EventArgs()); 27 | } 28 | 29 | private void DoWork(object sender, EventArgs e) 30 | { 31 | Console.WriteLine($"Event invoked by sender {sender}. Event arguments - {e}"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Rust/examples/example2.rs: -------------------------------------------------------------------------------- 1 | // Example 2: an EventEmitter with both events and payload being string slices. 2 | // 3 | // Note: the purpose of this example is just to demonstrate that it works and 4 | // that our EventEmitter is generic enough to handle this. However, it is more 5 | // of a counter-example that shows how you should not write code in Rust. 6 | // Instead, leverage the type system. Create an enum with possible event types 7 | // (see the previous example), and use it with all of the static correctness 8 | // guarantees. Avoid so called "stringly-typed APIs" in statically typed 9 | // languages. 10 | 11 | extern crate event_emitter; 12 | 13 | use event_emitter::EventEmitter; 14 | 15 | fn main() { 16 | let mut emitter = EventEmitter::new(); 17 | 18 | emitter.on("hello", |name| { 19 | println!("Hello, {}!", name); 20 | }); 21 | 22 | emitter.on("hello", |name| { 23 | println!("Someone said hello to {}.", name); 24 | }); 25 | 26 | emitter.on("bye", |name| { 27 | println!("Bye, {}, hope to see you again!", name); 28 | }); 29 | 30 | emitter.emit("hello", "Alex"); 31 | emitter.emit("bye", "Alex"); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2024 How.Programming.Works contributors 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 | -------------------------------------------------------------------------------- /Rust/examples/example4.rs: -------------------------------------------------------------------------------- 1 | // Example 4: using emitters of single events. 2 | // 3 | // As we've seen in the previous example, using the statically typed 4 | // `EventEmitter` gets pretty tedious when we need different payload types, so 5 | // let's make the emitter represent a single event type. Then we can parametrize 6 | // it with payload type easily, and we still can group events into 7 | // EventEmitter-like entities using regular structs. 8 | 9 | extern crate event_emitter; 10 | 11 | use event_emitter::Event; 12 | 13 | #[derive(Default)] 14 | struct AppEvents { 15 | click: Event<(i32, i32)>, 16 | key_press: Event, 17 | close: Event<()>, 18 | } 19 | 20 | fn main() { 21 | let mut events = AppEvents::default(); 22 | 23 | events.click.subscribe(|&(x, y)| { 24 | println!("Clicked on ({}, {})", x, y); 25 | }); 26 | 27 | events.key_press.subscribe(|key| { 28 | println!("Pressed \"{}\"", key); 29 | }); 30 | 31 | events.close.subscribe(|_| { 32 | println!("Window closed, cleaning up"); 33 | }); 34 | 35 | events.key_press.emit('a'); 36 | events.click.emit((100, 200)); 37 | events.close.emit(()); 38 | } 39 | -------------------------------------------------------------------------------- /Rust/tests/event.rs: -------------------------------------------------------------------------------- 1 | extern crate event_emitter; 2 | 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | use event_emitter::Event; 7 | 8 | #[derive(Default)] 9 | struct Events { 10 | first: Event, 11 | second: Event, 12 | } 13 | 14 | #[test] 15 | fn event() { 16 | let mut events = Events::default(); 17 | 18 | let a_value = Rc::new(RefCell::new(0)); 19 | let a_count = Rc::new(RefCell::new(0)); 20 | let b_value = Rc::new(RefCell::new(0)); 21 | 22 | { 23 | let a_value = Rc::clone(&a_value); 24 | 25 | events.first.subscribe(move |&value| { 26 | *a_value.borrow_mut() += value; 27 | }); 28 | } 29 | 30 | { 31 | let a_count = Rc::clone(&a_count); 32 | 33 | events.first.subscribe(move |_| { 34 | *a_count.borrow_mut() += 1; 35 | }); 36 | } 37 | 38 | { 39 | let b_value = Rc::clone(&b_value); 40 | 41 | events.second.subscribe(move |&value| { 42 | *b_value.borrow_mut() += value; 43 | }); 44 | } 45 | 46 | events.first.emit(10); 47 | events.first.emit(20); 48 | events.second.emit(30); 49 | events.second.emit(40); 50 | 51 | assert_eq!(30, *a_value.borrow()); 52 | assert_eq!(2, *a_count.borrow()); 53 | assert_eq!(70, *b_value.borrow()); 54 | } 55 | -------------------------------------------------------------------------------- /Rust/tests/event_emitter.rs: -------------------------------------------------------------------------------- 1 | extern crate event_emitter; 2 | 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | use event_emitter::EventEmitter; 7 | 8 | #[derive(Hash, Eq, PartialEq)] 9 | enum Event { 10 | A, 11 | B, 12 | } 13 | 14 | #[test] 15 | fn event_emitter() { 16 | let mut emitter = EventEmitter::new(); 17 | 18 | let a_value = Rc::new(RefCell::new(0)); 19 | let a_count = Rc::new(RefCell::new(0)); 20 | let b_value = Rc::new(RefCell::new(0)); 21 | 22 | { 23 | let a_value = Rc::clone(&a_value); 24 | 25 | emitter.on(Event::A, move |&value| { 26 | *a_value.borrow_mut() += value; 27 | }); 28 | } 29 | 30 | { 31 | let a_count = Rc::clone(&a_count); 32 | 33 | emitter.on(Event::A, move |_| { 34 | *a_count.borrow_mut() += 1; 35 | }); 36 | } 37 | 38 | { 39 | let b_value = Rc::clone(&b_value); 40 | 41 | emitter.on(Event::B, move |&value| { 42 | *b_value.borrow_mut() += value; 43 | }); 44 | } 45 | 46 | emitter.emit(Event::A, 10); 47 | emitter.emit(Event::A, 20); 48 | emitter.emit(Event::B, 30); 49 | emitter.emit(Event::B, 40); 50 | 51 | assert_eq!(30, *a_value.borrow()); 52 | assert_eq!(2, *a_count.borrow()); 53 | assert_eq!(70, *b_value.borrow()); 54 | } 55 | -------------------------------------------------------------------------------- /Rust/examples/example3.rs: -------------------------------------------------------------------------------- 1 | // Example 3: making the payload an enum too, so that we can use different 2 | // payload types for different events. (We'll see how we can make this code more 3 | // simple and readable by using a different abstraction in the next example.) 4 | 5 | extern crate event_emitter; 6 | 7 | use event_emitter::EventEmitter; 8 | 9 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] 10 | enum Event { 11 | Click, 12 | KeyPress, 13 | Close, 14 | } 15 | 16 | #[derive(Debug, Clone)] 17 | enum Payload { 18 | None, 19 | ClickCoords(i32, i32), 20 | KeyChar(char), 21 | } 22 | 23 | use Event::{Click, Close, KeyPress}; 24 | use Payload::{ClickCoords, KeyChar}; 25 | 26 | fn main() { 27 | let mut emitter = EventEmitter::new(); 28 | 29 | emitter.on(Click, |payload| { 30 | let (x, y) = match *payload { 31 | ClickCoords(x, y) => (x, y), 32 | _ => unreachable!(), 33 | }; 34 | println!("Clicked on ({}, {})", x, y); 35 | }); 36 | 37 | emitter.on(KeyPress, |payload| { 38 | let key = match *payload { 39 | KeyChar(c) => c, 40 | _ => unreachable!(), 41 | }; 42 | println!("Pressed \"{}\"", key); 43 | }); 44 | 45 | emitter.on(Close, |_| { 46 | println!("Window closed, cleaning up"); 47 | }); 48 | 49 | emitter.emit(KeyPress, KeyChar('a')); 50 | emitter.emit(Click, ClickCoords(100, 200)); 51 | emitter.emit(Close, Payload::None); 52 | } 53 | -------------------------------------------------------------------------------- /JavaScript/9-min.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = (l, o) => (l = {}, o = { 4 | on: (n, f) => (l[n] = l[n] || []).push(f), 5 | emit: (n, ...d) => (l[n] || []).map((f) => f(...d)), 6 | once: (n, f, g) => o.on(n, g = (...a) => (f(...a), o.remove(n, g))), 7 | remove: (n, f, e) => (e = l[n] || [], e.splice(e.indexOf(f), 1)), 8 | clear: (n) => (n ? delete l[n] : l = {}), 9 | count: (n) => (l[n] || []).length, 10 | listeners: (n) => (l[n] || []).slice(), 11 | names: () => Object.keys(l), 12 | }); 13 | 14 | // Usage 15 | 16 | const ee = emitter(); 17 | 18 | // on and emit 19 | 20 | ee.on('e1', (data) => { 21 | console.dir(data); 22 | }); 23 | 24 | ee.emit('e1', { msg: 'e1 ok' }); 25 | 26 | // once 27 | 28 | ee.once('e2', (data) => { 29 | console.dir(data); 30 | }); 31 | 32 | ee.emit('e2', { msg: 'e2 ok' }); 33 | ee.emit('e2', { msg: 'e2 not ok' }); 34 | 35 | // remove 36 | 37 | const f3 = (data) => { 38 | console.dir(data); 39 | }; 40 | 41 | ee.on('e3', f3); 42 | ee.remove('e3', f3); 43 | ee.emit('e3', { msg: 'e3 not ok' }); 44 | 45 | // count 46 | 47 | ee.on('e4', () => {}); 48 | ee.on('e4', () => {}); 49 | console.log('e4 count', ee.count('e4')); 50 | 51 | // clear 52 | 53 | ee.clear('e4'); 54 | ee.emit('e4', { msg: 'e4 not ok' }); 55 | ee.emit('e1', { msg: 'e1 ok' }); 56 | 57 | ee.clear(); 58 | ee.emit('e1', { msg: 'e1 not ok' }); 59 | 60 | // listeners and names 61 | 62 | ee.on('e5', () => {}); 63 | ee.on('e5', () => {}); 64 | ee.on('e6', () => {}); 65 | ee.on('e7', () => {}); 66 | 67 | console.log('listeners', ee.listeners('e5')); 68 | console.log('names', ee.names()); 69 | -------------------------------------------------------------------------------- /CSharp/ConsoleEventsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HowProgrammingWorks.CSharpEvents 8 | { 9 | public static class ConsoleEventsTest 10 | { 11 | public static void SimpleEventFlowTest() 12 | { 13 | var simpleEventFlow = new SimpleEventFlow(); 14 | 15 | simpleEventFlow.Subscribe(); 16 | simpleEventFlow.Invoke(); 17 | simpleEventFlow.Unsubscribe(); 18 | 19 | try 20 | { 21 | //Throw exception, because there is no handlers 22 | simpleEventFlow.Invoke(); 23 | } 24 | catch (Exception ex) 25 | { 26 | Console.WriteLine($"Exception happend. Exception message - {ex.Message}"); 27 | } 28 | 29 | Console.ReadLine(); 30 | } 31 | 32 | public static void JsStyleEmitterTest() 33 | { 34 | var emitter = new JsStyleEventEmitter(); 35 | emitter.On("smth", (o => 36 | { 37 | Console.WriteLine("1"); 38 | })); 39 | emitter.On("smth2", (o => 40 | { 41 | Console.WriteLine("2"); 42 | })); 43 | emitter.On("*", (o => 44 | { 45 | Console.WriteLine("3"); 46 | })); 47 | 48 | //Emit only 'smth' 49 | emitter.Emit("smth", new object()); 50 | 51 | //Emit all events 52 | emitter.Emit("*", new object()); 53 | 54 | Console.ReadLine(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Rust/src/event.rs: -------------------------------------------------------------------------------- 1 | /// Single event emitter. 2 | /// 3 | /// Contrary to the Node.js-like `EventEmitter`, this one isn't parametrized 4 | /// with the event type, because it, basically, represents the event itself. 5 | /// 6 | /// Such design gives us more flexibility without sacrificing type safety. 7 | /// Events that are logically bound together can still be grouped into, e.g., a 8 | /// struct, but they don't have to share the same payload type. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// # use event_emitter::Event; 14 | /// # 15 | /// #[derive(Default)] 16 | /// struct Events { 17 | /// first: Event<&'static str>, 18 | /// second: Event, 19 | /// } 20 | /// 21 | /// let mut events = Events::default(); 22 | /// 23 | /// events.first.subscribe(|&data| { 24 | /// assert_eq!("data for a", data); 25 | /// }); 26 | /// 27 | /// events.second.subscribe(|&data| { 28 | /// assert_eq!(42, data); 29 | /// }); 30 | /// 31 | /// events.first.emit("data for a"); 32 | /// events.second.emit(42); 33 | /// ``` 34 | pub struct Event { 35 | handlers: Vec>, 36 | } 37 | 38 | impl Event { 39 | /// Creates a new event with no handlers. 40 | pub fn new() -> Self { 41 | Self { handlers: vec![] } 42 | } 43 | 44 | /// Registers an event handler. There may be more that one handler at a 45 | /// time. 46 | pub fn subscribe(&mut self, handler: F) 47 | where 48 | F: Fn(&T) + 'static, 49 | { 50 | self.handlers.push(Box::new(handler)); 51 | } 52 | 53 | /// Fires the event, passing the `payload` as an argument of each event 54 | /// handler. 55 | pub fn emit(&self, payload: T) { 56 | for handler in &self.handlers { 57 | handler(&payload); 58 | } 59 | } 60 | } 61 | 62 | impl Default for Event { 63 | fn default() -> Self { 64 | Self::new() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Rust/README.md: -------------------------------------------------------------------------------- 1 | # EventEmitter Example in Rust 2 | 3 | [\[docs\]](https://doc-oqwooorcsj.now.sh/event\_emitter/) 4 | 5 | ![Ferris the Crab](https://camo.githubusercontent.com/3116127f2324db6e99bf00425e6808132a810624/68747470733a2f2f7a696d6167652e676c6f62616c2e73736c2e666173746c792e6e65742f3f75726c3d687474703a2f2f7777772e72757374616365616e2e6e65742f6173736574732f72757374616365616e2d666c61742d68617070792e706e6726773d323030) 6 | 7 | ## Installing Rust 8 | 9 | Download `rustup` from and run 10 | 11 | ```console 12 | rustup install stable 13 | ``` 14 | 15 | ## Running examples 16 | 17 | Run `cargo run --example name_of_the_example`. E.g.: 18 | 19 | ```console 20 | cargo run --example example1 21 | ``` 22 | 23 | ## Running tests 24 | 25 | There are two types of tests that are used in this crate: integration tests (the 26 | reside in the `tests/` directory) and doc-tests (in Rust, examples in 27 | documentation are tests too). Besides these, `cargo` also supports unit tests, 28 | but there aren't any examples of them in this project. 29 | 30 | Type 31 | 32 | ```console 33 | cargo test 34 | ``` 35 | 36 | to run all the tests of all types that are present in the project. 37 | 38 | ## Building documentation 39 | 40 | ```console 41 | cargo doc 42 | ``` 43 | 44 | will build the documentation, and 45 | 46 | ```console 47 | cargo doc --open 48 | ``` 49 | 50 | will build it and open it in your browser. 51 | 52 | ## Contributing 53 | 54 | Formatting your code using [rustfmt-nightly][] and fixing [clippy][] warnings, 55 | if there are any, is much appreciated 🐱 56 | 57 | Please note that, at the time of writing, you need a nightly compiler for these: 58 | 59 | ```console 60 | rustup install nightly 61 | cargo +nightly install rustfmt-nightly clippy 62 | 63 | cargo +nightly fmt 64 | cargo +nightly clippy 65 | ``` 66 | 67 | [clippy]: https://github.com/rust-lang-nursery/rust-clippy 68 | [rustfmt-nightly]: https://github.com/rust-lang-nursery/rustfmt 69 | -------------------------------------------------------------------------------- /Rust/src/event_emitter.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::cmp::Eq; 3 | use std::hash::Hash; 4 | 5 | type HandlerPtr = Box; 6 | 7 | /// Node.js-like event emitter. 8 | /// 9 | /// # Example 10 | /// 11 | /// ``` 12 | /// # use event_emitter::EventEmitter; 13 | /// # 14 | /// #[derive(Hash, Eq, PartialEq)] 15 | /// enum Event { 16 | /// A, 17 | /// B, 18 | /// } 19 | /// 20 | /// let mut emitter = EventEmitter::new(); 21 | /// 22 | /// emitter.on(Event::A, |&data| { 23 | /// assert_eq!("data for A", data); 24 | /// }); 25 | /// 26 | /// emitter.on(Event::B, |&data| { 27 | /// assert_eq!("data for B", data); 28 | /// }); 29 | /// 30 | /// emitter.emit(Event::A, "data for A"); 31 | /// emitter.emit(Event::B, "data for B"); 32 | /// ``` 33 | pub struct EventEmitter { 34 | handlers: HashMap>>, 35 | } 36 | 37 | impl EventEmitter { 38 | /// Creates a new instance of `EventEmitter`. 39 | pub fn new() -> Self { 40 | Self { 41 | handlers: HashMap::new(), 42 | } 43 | } 44 | 45 | /// Registers function `handler` as a listener for `event`. There may be 46 | /// multiple listeners for a single event. 47 | pub fn on(&mut self, event: T, handler: F) 48 | where 49 | F: Fn(&U) + 'static, 50 | { 51 | let event_handlers = 52 | self.handlers.entry(event).or_insert_with(|| vec![]); 53 | event_handlers.push(Box::new(handler)); 54 | } 55 | 56 | /// Invokes all listeners of `event`, passing a reference to `payload` as an 57 | /// argument to each of them. 58 | pub fn emit(&self, event: T, payload: U) { 59 | if let Some(handlers) = self.handlers.get(&event) { 60 | for handler in handlers { 61 | handler(&payload); 62 | } 63 | } 64 | } 65 | } 66 | 67 | impl Default for EventEmitter { 68 | fn default() -> Self { 69 | Self::new() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /JavaScript/8-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = () => { 4 | let events = {}; 5 | const ee = { 6 | on: (name, f, timeout = 0) => { 7 | const event = events[name] || []; 8 | events[name] = event; 9 | event.push(f); 10 | if (timeout) { 11 | setTimeout(() => { 12 | ee.remove(name, f); 13 | }, timeout); 14 | } 15 | }, 16 | emit: (name, ...data) => { 17 | const event = events[name]; 18 | if (event) event.forEach((f) => f(...data)); 19 | }, 20 | once: (name, f) => { 21 | const g = (...a) => { 22 | ee.remove(name, g); 23 | f(...a); 24 | }; 25 | ee.on(name, g); 26 | }, 27 | remove: (name, f) => { 28 | const event = events[name]; 29 | if (!event) return; 30 | const i = event.indexOf(f); 31 | if (i !== -1) event.splice(i, 1); 32 | }, 33 | clear: (name) => { 34 | if (name) delete events[name]; 35 | else events = {}; 36 | }, 37 | count: (name) => { 38 | const event = events[name]; 39 | return event ? event.length : 0; 40 | }, 41 | listeners: (name) => { 42 | const event = events[name]; 43 | return event.slice(); 44 | }, 45 | names: () => Object.keys(events), 46 | }; 47 | return ee; 48 | }; 49 | 50 | // Usage 51 | 52 | const ee = emitter(); 53 | 54 | // on and emit 55 | 56 | ee.on('e1', (data) => { 57 | console.dir(data); 58 | }); 59 | 60 | ee.emit('e1', { msg: 'e1 ok' }); 61 | 62 | // once 63 | 64 | ee.once('e2', (data) => { 65 | console.dir(data); 66 | }); 67 | 68 | ee.emit('e2', { msg: 'e2 ok' }); 69 | ee.emit('e2', { msg: 'e2 not ok' }); 70 | 71 | // remove 72 | 73 | const f3 = (data) => { 74 | console.dir(data); 75 | }; 76 | 77 | ee.on('e3', f3); 78 | ee.remove('e3', f3); 79 | ee.emit('e3', { msg: 'e3 not ok' }); 80 | 81 | // count 82 | 83 | ee.on('e4', () => {}); 84 | ee.on('e4', () => {}); 85 | console.log('e4 count', ee.count('e4')); 86 | 87 | // clear 88 | 89 | ee.clear('e4'); 90 | ee.emit('e4', { msg: 'e4 not ok' }); 91 | ee.emit('e1', { msg: 'e1 ok' }); 92 | 93 | ee.clear(); 94 | ee.emit('e1', { msg: 'e1 not ok' }); 95 | 96 | // listeners and names 97 | 98 | ee.on('e5', () => {}); 99 | ee.on('e5', () => {}); 100 | ee.on('e6', () => {}); 101 | ee.on('e7', () => {}); 102 | 103 | console.log('listeners', ee.listeners('e5')); 104 | console.log('names', ee.names()); 105 | -------------------------------------------------------------------------------- /Java/1-simple.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | import java.util.List; 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.ArrayList; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Simple event emitter that allows {@link Consumer} 10 | * objects to be registered as listeners. 11 | * Can emit events, register and delete listeners. 12 | * 13 | * @param type of the listener's parameter 14 | */ 15 | class SimpleEventEmitter { 16 | /** 17 | * Map with all registered events and corresponding listeners. 18 | */ 19 | private final Map>> events; 20 | 21 | protected SimpleEventEmitter() { 22 | this.events = new HashMap<>(); 23 | } 24 | 25 | /** 26 | * Adds new listener to the given event. 27 | * 28 | * @param name event's name 29 | * @param listener action listener 30 | */ 31 | public void on(final String name, final Consumer listener) { 32 | List> listeners = Optional.ofNullable(events.get(name)) 33 | .orElseGet(ArrayList::new); 34 | listeners.add(listener); 35 | events.put(name, listeners); 36 | } 37 | 38 | /** 39 | * Fires event with given data, executing all event listeners. 40 | * 41 | * @param name event's name 42 | * @param data event's data 43 | */ 44 | public void emit(final String name, final T data) { 45 | Optional.ofNullable(events.get(name)) 46 | .ifPresent((listeners) -> listeners 47 | .forEach((listener) -> listener.accept(data))); 48 | } 49 | 50 | /** 51 | * Removes event listener from specified event. 52 | * 53 | * @param name event's name 54 | * @param listener event listener 55 | * @return {@code true} if the event existed and the listener 56 | * was registered to that event, {@code false} otherwise 57 | */ 58 | public boolean remove(final String name, final Consumer listener) { 59 | return Optional.ofNullable(events.get(name)) 60 | .map((listeners) -> listeners.remove(listener)) 61 | .orElse(false); 62 | } 63 | 64 | /** 65 | * Removes all event listeners from specified event. 66 | * 67 | * @param name event's name 68 | * @return {@code true} if the event existed, {@code false} otherwise 69 | */ 70 | public boolean removeAll(final String name) { 71 | return events.remove(name) != null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /JavaScript/b-class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class EventEmitter { 4 | constructor() { 5 | this.events = new Map(); 6 | this.wrappers = new Map(); 7 | } 8 | 9 | on(name, fn) { 10 | const event = this.events.get(name); 11 | if (event) event.add(fn); 12 | else this.events.set(name, new Set([fn])); 13 | } 14 | 15 | once(name, fn) { 16 | const wrapper = (...args) => { 17 | this.remove(name, wrapper); 18 | fn(...args); 19 | }; 20 | this.wrappers.set(fn, wrapper); 21 | this.on(name, wrapper); 22 | } 23 | 24 | emit(name, ...args) { 25 | const event = this.events.get(name); 26 | if (!event) return; 27 | for (const fn of event.values()) { 28 | fn(...args); 29 | } 30 | } 31 | 32 | remove(name, fn) { 33 | const event = this.events.get(name); 34 | if (!event) return; 35 | if (event.has(fn)) { 36 | event.delete(fn); 37 | return; 38 | } 39 | const wrapper = this.wrappers.get(fn); 40 | if (wrapper) { 41 | event.delete(wrapper); 42 | if (event.size === 0) this.events.delete(name); 43 | } 44 | } 45 | 46 | clear(name) { 47 | if (name) this.events.delete(name); 48 | else this.events.clear(); 49 | } 50 | 51 | count(name) { 52 | const event = this.events.get(name); 53 | return event ? event.size : 0; 54 | } 55 | 56 | listeners(name) { 57 | const event = this.events.get(name); 58 | return new Set(event); 59 | } 60 | 61 | names() { 62 | return [...this.events.keys()]; 63 | } 64 | } 65 | 66 | // Usage 67 | 68 | const ee = new EventEmitter(); 69 | 70 | // on and emit 71 | 72 | ee.on('e1', (data) => { 73 | console.dir(data); 74 | }); 75 | 76 | ee.emit('e1', { msg: 'e1 ok' }); 77 | 78 | // once 79 | 80 | ee.once('e2', (data) => { 81 | console.dir(data); 82 | }); 83 | 84 | ee.emit('e2', { msg: 'e2 ok' }); 85 | ee.emit('e2', { msg: 'e2 not ok' }); 86 | 87 | // remove 88 | 89 | const f3 = (data) => { 90 | console.dir(data); 91 | }; 92 | 93 | ee.on('e3', f3); 94 | ee.remove('e3', f3); 95 | ee.emit('e3', { msg: 'e3 not ok' }); 96 | 97 | // count 98 | 99 | ee.on('e4', () => {}); 100 | ee.on('e4', () => {}); 101 | console.log('e4 count', ee.count('e4')); 102 | 103 | // clear 104 | 105 | ee.clear('e4'); 106 | ee.emit('e4', { msg: 'e4 not ok' }); 107 | ee.emit('e1', { msg: 'e1 ok' }); 108 | 109 | ee.clear(); 110 | ee.emit('e1', { msg: 'e1 not ok' }); 111 | 112 | // listeners and names 113 | 114 | ee.on('e5', () => {}); 115 | ee.on('e5', () => {}); 116 | ee.on('e6', () => {}); 117 | ee.on('e7', () => {}); 118 | 119 | console.log('listeners', ee.listeners('e5')); 120 | console.log('names', ee.names()); 121 | -------------------------------------------------------------------------------- /JavaScript/a-prod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emitter = () => { 4 | const events = new Map(); 5 | const wrapped = new Map(); 6 | const ee = { 7 | on: (name, f, timeout = 0) => { 8 | const event = events.get(name); 9 | if (event) event.push(f); 10 | else events.set(name, [f]); 11 | if (timeout) { 12 | setTimeout(() => { 13 | ee.remove(name, f); 14 | }, timeout); 15 | } 16 | }, 17 | emit: (name, ...data) => { 18 | const event = events.get(name); 19 | if (event) event.forEach((f) => f(...data)); 20 | }, 21 | once: (name, f) => { 22 | const g = (...a) => { 23 | ee.remove(name, g); 24 | f(...a); 25 | }; 26 | wrapped.set(f, g); 27 | ee.on(name, g); 28 | }, 29 | remove: (name, f) => { 30 | const event = events.get(name); 31 | if (!event) return; 32 | let i = event.indexOf(f); 33 | if (i !== -1) { 34 | event.splice(i, 1); 35 | return; 36 | } 37 | const g = wrapped.get(f); 38 | if (g) { 39 | i = event.indexOf(g); 40 | if (i !== -1) event.splice(i, 1); 41 | if (!event.length) events.delete(name); 42 | } 43 | }, 44 | clear: (name) => { 45 | if (name) events.delete(name); 46 | else events.clear(); 47 | }, 48 | count: (name) => { 49 | const event = events.get(name); 50 | return event ? event.length : 0; 51 | }, 52 | listeners: (name) => { 53 | const event = events.get(name); 54 | return event.slice(); 55 | }, 56 | names: () => [...events.keys()], 57 | }; 58 | return ee; 59 | }; 60 | 61 | // Usage 62 | 63 | const ee = emitter(); 64 | 65 | // on and emit 66 | 67 | ee.on('e1', (data) => { 68 | console.dir(data); 69 | }); 70 | 71 | ee.emit('e1', { msg: 'e1 ok' }); 72 | 73 | // once 74 | 75 | ee.once('e2', (data) => { 76 | console.dir(data); 77 | }); 78 | 79 | ee.emit('e2', { msg: 'e2 ok' }); 80 | ee.emit('e2', { msg: 'e2 not ok' }); 81 | 82 | // remove 83 | 84 | const f3 = (data) => { 85 | console.dir(data); 86 | }; 87 | 88 | ee.on('e3', f3); 89 | ee.remove('e3', f3); 90 | ee.emit('e3', { msg: 'e3 not ok' }); 91 | 92 | // count 93 | 94 | ee.on('e4', () => {}); 95 | ee.on('e4', () => {}); 96 | console.log('e4 count', ee.count('e4')); 97 | 98 | // clear 99 | 100 | ee.clear('e4'); 101 | ee.emit('e4', { msg: 'e4 not ok' }); 102 | ee.emit('e1', { msg: 'e1 ok' }); 103 | 104 | ee.clear(); 105 | ee.emit('e1', { msg: 'e1 not ok' }); 106 | 107 | // listeners and names 108 | 109 | ee.on('e5', () => {}); 110 | ee.on('e5', () => {}); 111 | ee.on('e6', () => {}); 112 | ee.on('e7', () => {}); 113 | 114 | console.log('listeners', ee.listeners('e5')); 115 | console.log('names', ee.names()); 116 | -------------------------------------------------------------------------------- /JavaScript/c-async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class AsyncEmitter { 4 | constructor() { 5 | this.events = new Map(); 6 | } 7 | 8 | event(name) { 9 | let event = this.events.get(name); 10 | if (event) return event; 11 | const on = new Set(); 12 | const once = new Set(); 13 | event = { on, once }; 14 | this.events.set(name, event); 15 | return event; 16 | } 17 | 18 | on(name, fn) { 19 | this.event(name).on.add(fn); 20 | } 21 | 22 | once(name, fn) { 23 | if (fn === undefined) { 24 | return new Promise((resolve) => { 25 | this.once(name, resolve); 26 | }); 27 | } 28 | this.event(name).once.add(fn); 29 | return null; 30 | } 31 | 32 | async emit(name, ...args) { 33 | const event = this.events.get(name); 34 | if (!event) return null; 35 | const { on, once } = event; 36 | const aon = [...on.values()]; 37 | const aonce = [...once.values()]; 38 | const promises = aon.concat(aonce).map((fn) => fn(...args)); 39 | once.clear(); 40 | if (on.size === 0 && once.size === 0) { 41 | this.events.delete(name); 42 | } 43 | return Promise.all(promises); 44 | } 45 | 46 | remove(name, fn) { 47 | const { events } = this; 48 | const event = events.get(name); 49 | if (!event) return; 50 | const { on, once } = event; 51 | on.delete(fn); 52 | once.delete(fn); 53 | if (on.size === 0 && once.size === 0) { 54 | this.events.delete(name); 55 | } 56 | } 57 | 58 | clear(name) { 59 | const { events } = this; 60 | if (!name) { 61 | events.clear(); 62 | return; 63 | } 64 | const event = events.get(name); 65 | if (event) events.delete(name); 66 | } 67 | 68 | count(name) { 69 | const event = this.events.get(name); 70 | if (!event) return 0; 71 | const { on, once } = event; 72 | return on.size + once.size; 73 | } 74 | 75 | listeners(name) { 76 | const event = this.events.get(name); 77 | if (!event) return []; 78 | const { on, once } = event; 79 | return [...on.values(), ...once.values()]; 80 | } 81 | 82 | names() { 83 | return [...this.events.keys()]; 84 | } 85 | } 86 | 87 | // Usage 88 | 89 | const main = async () => { 90 | const ee = new AsyncEmitter(); 91 | 92 | ee.on('e1', async () => { 93 | console.log('e1 listener 1'); 94 | }); 95 | 96 | ee.on('e1', async () => { 97 | console.log('e1 listener 2'); 98 | }); 99 | 100 | ee.on('e1', async () => { 101 | console.log('e1 listener 3'); 102 | }); 103 | 104 | ee.on('e2', async () => { 105 | console.log('e2 listener 1'); 106 | }); 107 | 108 | ee.on('e2', async () => { 109 | console.log('e2 listener 2'); 110 | }); 111 | 112 | console.log('begin'); 113 | await ee.emit('e1'); 114 | await ee.emit('e2'); 115 | console.log('end'); 116 | }; 117 | 118 | main(); 119 | -------------------------------------------------------------------------------- /CSharp/JsStyleEventEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HowProgrammingWorks.CSharpEvents 8 | { 9 | public class JsStyleEventEmitter 10 | { 11 | 12 | private readonly Dictionary>> _eventsDictionary; 13 | 14 | public JsStyleEventEmitter() 15 | { 16 | this._eventsDictionary = new Dictionary>>(); 17 | } 18 | 19 | public void On(string name, Action action) 20 | { 21 | List> subscribedActions; 22 | if (_eventsDictionary.TryGetValue(name, out subscribedActions)) 23 | { 24 | subscribedActions.Add(action); 25 | } 26 | else 27 | { 28 | _eventsDictionary.Add(name, new List> { action }); 29 | } 30 | } 31 | 32 | public void Emit(string name, object data) 33 | { 34 | if (name == "*") 35 | { 36 | foreach (var currentEvent in _eventsDictionary) 37 | { 38 | foreach (var action in currentEvent.Value) 39 | { 40 | action(data); 41 | } 42 | } 43 | } 44 | else 45 | { 46 | 47 | List> subscribedActions; 48 | if (!_eventsDictionary.TryGetValue(name, out subscribedActions)) 49 | { 50 | Console.WriteLine("Action does not exist"); 51 | } 52 | else 53 | { 54 | foreach (var action in subscribedActions) 55 | { 56 | action(data); 57 | } 58 | } 59 | } 60 | } 61 | 62 | public void RemoveListener(string name, Action action) 63 | { 64 | List> subscribedActions; 65 | if (!this._eventsDictionary.TryGetValue(name, out subscribedActions)) 66 | { 67 | Console.WriteLine("Action does not exist"); 68 | } 69 | else 70 | { 71 | var currentEvent = subscribedActions.Exists(e => e == action); 72 | if (currentEvent == false) 73 | { 74 | Console.WriteLine("Event does not exist"); 75 | } 76 | else 77 | { 78 | subscribedActions.Remove(action); 79 | } 80 | } 81 | } 82 | 83 | public void RemoveAllListeners(string name) 84 | { 85 | List> subscribedActions; 86 | if (!this._eventsDictionary.TryGetValue(name, out subscribedActions)) 87 | { 88 | Console.WriteLine("Action does not exist"); 89 | } 90 | else 91 | { 92 | subscribedActions.RemoveAll(x => x != null); 93 | } 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /JavaScript/tasks.txt: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | 5 | - on(name, f, timeout = 0) 6 | - удаление событие через remove созданных через once 7 | - distinct() - метод переключает emitter в режим уникальных обработчиков 8 | - names() : array of string - возвращаем все имена 9 | - listeners(name): array of function - возвращает копию массива подписок 10 | 11 | - has(name) : boolean - существуют ли обработчики 12 | - has(name, f): boolean - есть ли функция f в массиве обработчиков 13 | - prepend(name, f) - устанавливает обработчик перед всеми остальными 14 | - insert(name, f, g) - устанавливает обработчик f перед g 15 | 16 | ------------------ 17 | 18 | - wrapper, EventEmitter, mixin 19 | - wrapper, factory, prototype 20 | - factory, mixin, functor 21 | - prototype, functor, class 22 | - mixin, class, factory 23 | 24 | 25 | 26 | 27 | 28 | */ 29 | 30 | const emitter = () => { 31 | let events = {}; 32 | const ee = { 33 | on: (name, f, timeout = 0) => { 34 | const event = events[name] || []; 35 | events[name] = event; 36 | event.push(f); 37 | if (timeout) setTimeout(() => { 38 | ee.remove(name, f); 39 | }, timeout); 40 | }, 41 | emit: (name, ...data) => { 42 | const event = events[name]; 43 | if (event) event.forEach(f => f(...data)); 44 | }, 45 | once: (name, f) => { 46 | const g = (...a) => { 47 | ee.remove(name, g); 48 | f(...a); 49 | }; 50 | ee.on(name, g); 51 | }, 52 | remove: (name, f) => { 53 | const event = events[name]; 54 | if (!event) return; 55 | const i = event.indexOf(f); 56 | event.splice(i, 1); 57 | }, 58 | clear: (name) => { 59 | if (name) events[name] = []; 60 | else events = {}; 61 | }, 62 | count: (name) => { 63 | const event = events[name]; 64 | return event ? event.length : 0; 65 | }, 66 | listeners: (name) => { 67 | const event = events[name]; 68 | return event.slice(); 69 | }, 70 | names: () => Object.keys(events) 71 | }; 72 | return ee; 73 | }; 74 | 75 | // Usage 76 | 77 | const ee = emitter(); 78 | 79 | // on and emit 80 | 81 | ee.on('e1', (data) => { 82 | console.dir(data); 83 | }); 84 | 85 | ee.emit('e1', { msg: 'e1 ok' }); 86 | 87 | // once 88 | 89 | ee.once('e2', (data) => { 90 | console.dir(data); 91 | }); 92 | 93 | ee.emit('e2', { msg: 'e2 ok' }); 94 | ee.emit('e2', { msg: 'e2 not ok' }); 95 | 96 | // remove 97 | 98 | const f3 = (data) => { 99 | console.dir(data); 100 | }; 101 | 102 | ee.on('e3', f3); 103 | ee.remove('e3', f3); 104 | ee.emit('e3', { msg: 'e3 not ok' }); 105 | 106 | // count 107 | 108 | ee.on('e4', () => {}); 109 | ee.on('e4', () => {}); 110 | console.log('e4 count', ee.count('e4')); 111 | 112 | // clear 113 | 114 | ee.clear('e4'); 115 | ee.emit('e4', { msg: 'e4 not ok' }); 116 | ee.emit('e1', { msg: 'e1 ok' }); 117 | 118 | ee.clear(); 119 | ee.emit('e1', { msg: 'e1 not ok' }); 120 | 121 | // listeners and names 122 | 123 | ee.on('e5', () => {}); 124 | ee.on('e5', () => {}); 125 | ee.on('e6', () => {}); 126 | ee.on('e7', () => {}); 127 | 128 | console.log('listeners', ee.listeners('e5')); 129 | console.log('names', ee.names()); 130 | --------------------------------------------------------------------------------