SHARE
TWEET

Evolution

a guest Oct 20th, 2019 81 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { Log } from "./log";
  2. import { Helpers } from "./helpers";
  3.  
  4. export interface IEvent {
  5.   constructor: any;
  6.   //   someRequiredProp: string;
  7.   //   someOptionalProp?: string;
  8. }
  9.  
  10. interface IEventHandler<E extends IEvent> {
  11.   handle: (event: E) => Promise<boolean>;
  12. }
  13.  
  14. export abstract class EventHandler<E extends IEvent> implements IEventHandler<E> {
  15.   constructor(public eventClass: Constructor<E>) {}
  16.   abstract handle(event: E): Promise<boolean>;
  17. }
  18.  
  19. // source: https://dev.to/krumpet/generic-type-guard-in-typescript-258l
  20. type Constructor<T> = { new (...args: any[]): T };
  21.  
  22. export interface IEventDispatcher {
  23.   subscribe<E extends IEvent>(handler: IEventHandler<E>): boolean;
  24.   dispatch(event: IEvent, payload: any): Promise<void>;
  25.   unsubscribe(eventClass: Constructor<IEvent>): boolean;
  26. }
  27.  
  28. export class EventDispatcher implements IEventDispatcher {
  29.   /**
  30.    * All handlers in each array of handlers run asynchronously (at the same time).
  31.    * Handlers in each array will be executed only if every handler in the previous array succeeded.
  32.    *
  33.    * @type {Set<EventHandler<IEvent>[]>}
  34.    */
  35.   asyncEventHandlers: Set<EventHandler<IEvent>[]> = new Set<EventHandler<IEvent>[]>();
  36.  
  37.   /**
  38.    * Subscribes handlers to handle events asyncronously.
  39.    * If any handler in the group fails, any subsequent subscribed-handlers will not run.
  40.    * Add handlers in the order that they should be executed.
  41.    *
  42.    * @template E
  43.    * @param {...EventHandler<E>[]} handlers
  44.    * @returns {boolean}
  45.    */
  46.   subscribe<E extends IEvent>(...handlers: EventHandler<E>[]): boolean {
  47.     const sizeBefore = this.asyncEventHandlers.size;
  48.     const sizeAfter = this.asyncEventHandlers.add(handlers).size;
  49.     const result = sizeAfter > sizeBefore;
  50.     if (!result) {
  51.       handlers.forEach(handler => {
  52.         throw new Error(`Failed to subscribe event handler ${handler.constructor.name}.`);
  53.       });
  54.     }
  55.     handlers.forEach(handler => {
  56.       Log.info(`Subscribed event handler ${handler.constructor.name}.`);
  57.     });
  58.     return result;
  59.   }
  60.  
  61.   async dispatch(event: IEvent): Promise<void> {
  62.     Log.info(`Dispatching event ${event.constructor.name}...`);
  63.     const handlers = Array.from(this.asyncEventHandlers).map(sameEventHandlers =>
  64.       sameEventHandlers.filter(handler => handler.eventClass.name === event.constructor.name)
  65.     );
  66.     handlerGroup: for (const sameEventHandlers of handlers) {
  67.       const handles = sameEventHandlers.map(seh => ({
  68.         handler: seh,
  69.         eventClass: seh.eventClass,
  70.         handleFn: seh.handle
  71.       }));
  72.       const results = await Promise.all(handles.map(handling => handling.handleFn(event)));
  73.       for (const [i, result] of results.entries()) {
  74.         const handle = handles[i];
  75.         if (!result) {
  76.           Log.info(`Failed to handle ${handle.eventClass.name} with handler ${handle.handler.constructor.name}. Stopping futher events.`);
  77.           break handlerGroup;
  78.         }
  79.         Log.info(`Handled event ${handle.eventClass.name} with handler ${handle.handler.constructor.name}.`);
  80.       }
  81.     }
  82.   }
  83.  
  84.   // unsubscribe(eventClass: Constructor<IEvent>): boolean {
  85.   //   const event = Array.from(this.eventHandlers).find(handler => handler.eventClass instanceof eventClass); // TODO: Fix this
  86.   //   if (!event) {
  87.   //     throw new Error(`Cannot unsubscribe event ${eventClass.name} because it is not subscribed.`);
  88.   //   }
  89.   //   const result = this.eventHandlers.delete(event);
  90.   //   if (!result) {
  91.   //     throw new Error(`Error trying to unsubscribe event ${event.constructor.name}.`);
  92.   //   }
  93.   //   Log.info(`Unsubscribed event ${event.constructor.name}.`);
  94.   //   return result;
  95.   // }
  96.  
  97.   unsubscribe(eventClass: Constructor<IEvent>): boolean {
  98.     throw new Error("Method not implemented.");
  99.   }
  100. }
  101.  
  102. /** example concrete classes */
  103. class LeaderboardReadyEvent implements IEvent {
  104.   constructor(public winnerTeamId: number) {}
  105.   someRequiredProp: string;
  106. }
  107.  
  108. class SomeOtherEvent implements IEvent {
  109.   constructor(public someString: string) {}
  110.   someRequiredProp: string;
  111. }
  112.  
  113. class SomePlainClass {}
  114.  
  115. class DiscordLeaderboardDeliverer extends EventHandler<LeaderboardReadyEvent> {
  116.   async handle(event: LeaderboardReadyEvent): Promise<boolean> {
  117.     Log.info(`Delivering LeaderboardReadyEvent to Discord - winnerTeamId ${event.winnerTeamId}`);
  118.     return new Promise(async (resolve, reject) => {
  119.       try {
  120.         setTimeout(() => {
  121.           return resolve(true);
  122.         }, Helpers.getRandomInt(300, 800));
  123.       } catch (error) {
  124.         return reject(error);
  125.       }
  126.     });
  127.   }
  128. }
  129.  
  130. class WebLeaderboardDeliverer extends EventHandler<LeaderboardReadyEvent> {
  131.   async handle(event: LeaderboardReadyEvent): Promise<boolean> {
  132.     Log.info(`Delivering LeaderboardReadyEvent to Web - winnerTeamId ${event.winnerTeamId}`);
  133.     return new Promise(async (resolve, reject) => {
  134.       try {
  135.         setTimeout(() => {
  136.           const shouldRandomlyFail = Helpers.getRandomInt(1, 2) === 1 ? true : false;
  137.           if (shouldRandomlyFail) {
  138.             Log.warn("Failing WebLeaderboardDeliverer");
  139.             return resolve(false);
  140.           } else {
  141.             return resolve(true);
  142.           }
  143.         }, Helpers.getRandomInt(300, 800));
  144.       } catch (error) {
  145.         return reject(error);
  146.       }
  147.     });
  148.   }
  149. }
  150.  
  151. class LeaderboardDeliveredLogger extends EventHandler<LeaderboardReadyEvent> {
  152.   async handle(event: LeaderboardReadyEvent): Promise<boolean> {
  153.     Log.info(`Logging delivery success of LeaderboardReadyEvent with winnerTeamId ${event.winnerTeamId}`);
  154.     return new Promise(async (resolve, reject) => {
  155.       try {
  156.         setTimeout(() => {
  157.           return resolve(true);
  158.         }, Helpers.getRandomInt(300, 800));
  159.       } catch (error) {
  160.         return reject(error);
  161.       }
  162.     });
  163.   }
  164. }
  165.  
  166. class SomeOtherEventHandler extends EventHandler<SomeOtherEvent> {
  167.   async handle(event: SomeOtherEvent): Promise<boolean> {
  168.     Log.info(`Handling SomeOtherEventHandler with someString ${event.someString}`);
  169.     return new Promise(async (resolve, reject) => {
  170.       try {
  171.         setTimeout(() => {
  172.           return resolve(true);
  173.         }, Helpers.getRandomInt(300, 800));
  174.       } catch (error) {
  175.         return reject(error);
  176.       }
  177.     });
  178.   }
  179. }
  180.  
  181. setTimeout(async () => {
  182.   /** example implementation */
  183.   const dispatcher = new EventDispatcher();
  184.   const discordLeaderboardDeliverer = new DiscordLeaderboardDeliverer(LeaderboardReadyEvent);
  185.   const webLeaderboardDeliverer = new WebLeaderboardDeliverer(LeaderboardReadyEvent);
  186.   const leaderboardDeliveredLogger = new LeaderboardDeliveredLogger(LeaderboardReadyEvent);
  187.   dispatcher.subscribe<LeaderboardReadyEvent>(discordLeaderboardDeliverer, webLeaderboardDeliverer);
  188.   dispatcher.subscribe<LeaderboardReadyEvent>(leaderboardDeliveredLogger);
  189.   dispatcher.subscribe<SomeOtherEvent>(new SomeOtherEventHandler(SomeOtherEvent));
  190.   // await Promise.all([dispatcher.dispatch(new LeaderboardReadyEvent(111)), dispatcher.dispatch(new LeaderboardReadyEvent(222))]);
  191.   await dispatcher.dispatch(new LeaderboardReadyEvent(111));
  192.   // await dispatcher.dispatch(new LeaderboardReadyEvent(222));
  193.   // dispatcher.dispatch(new SomeOtherEvent("abc123"));
  194. }, 1);
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Not a member of Pastebin yet?
Sign Up, it unlocks many cool features!
 
Top