channel-event
TypeScript icon, indicating that this package has built-in type declarations

3.4.0 • Public • Published

channel-event

A simple and highly extensible javascript event channel and control flow library. It can run generator functions, which allows async data flows, simplifies cross component communication and hoists control flows to the parent scope. This allows creating highly modular and decoupled architectures.

Events are scoped to hub instances which means that the scale of the event channels can be controlled and event collisions are non existent. This makes for easy debugging.

Bundlephobia gzip + minified

Typescript typings are built in, however it will still work in vanilla javascript

Installation

yarn add channel-event

npn install channel-event

Use

First, create a hub

import { createHub } from "channel-event";

const hub = createHub({ enableLogging: process.env.NODE_ENV === "development" });

Then, create a channel. All channels created will be able to communicate with all other channels created by the same hub. Channels created by different hubs will not be able to communicate. If a channel is created with an id, that channel may return values from listen and those values will be returned to the sender channel in a dictionary { [channelId]: value }. Multiple channels can return values to a single sender, as long as their ids differ.

const channel = hub.newChannel();
const channel2 = hub.newChannel("channel id");

channel2.listen("test", data => {
   console.log(data); // -> { type: "test", payload: 10 }
   // since this channel was created with an id passed to newChannel
   // we can return a value here
   return true;
});

const result = channel.send("test", 10);
console.log(result); // -> { "channel id": true }

Generator functions allow for reusable async logic. See Generator actions for more generator actions.

function* test(): IterableIterator<EventIterable> {
   for (let count = 0; count < 10; count++) {
      // `put` is the equivelent of channel.send, called on the current channel
      yield put("test", count);
      // sleep for 200 ms
      yield delay(200);
   }
}

function* check(): IterableIterator<EventIterable> {
   for (let index = 0; index < 10; index++) {
      // `take` blocks until the specified event is 
      // sent within the context of the current hub
      const count = yield take("test");
      console.log(count);
   }
   console.log("done!");
}

// regular listen will pick up events sent from `put`
channel.listen("test", data => {
   console.log(data); // -> { type: "test", payload: <value of count> }
});

// this will print out the numbers 0-9 with 200 ms delays between prints
channel.generator
   .addGenerator(check)
   .addGenerator(test)
   .restartOnAsyncError()
   .run();

Call dispose when finished using

channel.dispose();
// or
hub.dispose(); // calls dispose on every channel inside the hub

Promises can also be yielded and the function will block until their completion.

const promResult = yield promiseObject;

Event middleware

Middleware can be added to the event chain to change the fundamental behaviour of events. Event middleware is added to the hub with addEventMiddleware. See https://github.com/zpxp/channel-store for an example of event middleware.

hub.addEventMiddleware((context, next) => {
   // log all events
   console.log(context.type);
   return next(context);
})

hub.addEventMiddleware((context, next) => {
   next(context);
   // override the return value from listens
   // channel.send(...) will now always return 42
   return 42;
})

Generator middleware

New generator actions can be defined by calling IHub.addGlobalGeneratorMiddleware or static IHub.addGlobalGeneratorMiddleware. The static function will add the new middleware to all future instances of IHub, while the instance function will only add the middleware to that hub instance. Whenever a generator yields an EventIterable, the hub will look for any middleware whos function name matches the EventIterable.function.

Generator middleware takes 2 arguments, the first contains all the arguments that the yielded function was called with, the second is the IChannel instance. Middleware must return a Promise, that when resolved, will return the resolved data from the yield statement.

export function pow(power: number): EventIterable {
   return {
      function: "power",
      value: { power: power }
   };
}


hub.addGeneratorMiddleware("power", function(data: EventIterable<{ power: number }>, channel: IChannel): Promise<any> {
   return Promise.resolve(Math.pow(42, data.value.power));
});

// the action can now be used like this
channel.runGenerator(function*(): IterableIterator<EventIterable> {
   const num = yield pow(2);
   console.log(num); // -> 1764
});

NOTE: If the promised returned from the generator middleware implementation is pending, it is a good idea to add the reject function to the channel.onDispose function to prevent hanging promises

hub.addGeneratorMiddleware("take", function(data: EventIterable<string | string[]>, channel: IChannel): Promise<any> {
   return new Promise((resolve, reject) => {
      const unsub = channel.listen(data.value, result => {
         unsub();
         resolve(result);
      });
      // call reject when channel is disposed
      channel.onDispose(reject);
   });
});

You may also create a new channel that exists for the scope of the generator action to make disposing all listeners easy, such as in this race action implentation

hub.addGeneratorMiddleware("race", function (data: EventIterable<Array<EventIterable | Promise<any>>>, channel): Promise<any> {
   // create an isolated channel that will be disposed on completion
   // all listeners that did not win the race need to be rejected
   // and cleaned up to prevent mem leaks
   const chan = channel.hub.newChannel();

   return Promise.race(
      data.value.map(item => {
         if (item instanceof Promise) {
            return item;
         } else {
            // is iterator. yield it and see what happens
            return new Promise((resolve, reject) => {
               chan.runGenerator(function*() {
                  return yield item;
               }, resolve);
               chan.onDispose(reject);
            });
         }
      })
   ).finally(() => {
      // when one action wins the race, dispose the channel 
      // cleaning up all pending listeners/generators
      chan.dispose();
   });
});

Generator actions

Current available actions implemented in all hub instances

/**
 * waits until the dispatch of a specified type then returns the data
 * @param type the string type or types to wait on
 */
export declare function take(type: string | string[]): EventIterable;

/**
 * Calls `channel.send` on the current hub
 * @param type action type
 * @param data optional data to send to all listeners
 */
export declare function put(type: string, data?: any): EventIterable;

/**
 * Calls an async func (promise) or generator func and waits until its completion, returning
 * the result
 * @param func async func (promise) or generator function
 * @param args
 */
export declare function call<A extends any[]>(func: (...args: A) => any, ...args: A): EventIterable;

/**
 * Same as `call` except does not block until the function returns. Returns a cancel function that will cancel the forked task
 * @param func plain function, async func (promise) or generator function
 * @param args
 */
export declare function fork<A extends any[]>(func: (...args: A) => any, ...args: A): EventIterable;

/**
 * Blocks for the given ms duration
 * @param durationMs
 */
export declare function delay(durationMs: number): EventIterable;

/**
 * Call `func` whenever `type` is dispatched. Cancels any existing instances `func` that may be running
 * @param type Call func whenever this type is dispatched
 * @param func
 */
export declare function takeLatest(type: string | string[], func: (data?: any) => IterableIterator<EventIterable>): EventIterable;

/**
 * Call `func` whenever `type` is dispatched.
 * @param type Call func whenever this type is dispatched
 * @param func
 */
export declare function takeEvery(type: string | string[], func: (data?: any) => IterableIterator<EventIterable>): EventIterable;

/**
 * Call `func` whenever `type` is dispatched if no instances of `func` are running
 * @param type Call func whenever this type is dispatched
 * @param func
 */
export declare function takeLast(type: string | string[], func: (data?: any) => IterableIterator<EventIterable>): EventIterable;

Extensions, middleware

Library Description
channel-store An event middleware that creates an ambient state for channel hubs that can be accessed anywhere that a channel exists
react-channel-event A react provider and HoC wrapper for channel-event
react-channel-store A react provider and HoC wrapper for channel-store

Package Sidebar

Install

npm i channel-event

Weekly Downloads

41

Version

3.4.0

License

MIT

Unpacked Size

70.5 kB

Total Files

18

Last publish

Collaborators

  • zpxp