suber

A message bus / pubsub compatible with Redux middlewares

  • suber

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
suber
2705.0.17 years ago7 years agoMinified + gzip package size for suber in KB

Readme

suber logo
Suber
- eventbus / pubsub compatible with Redux middlewares -

<img src="https://img.shields.io/npm/v/suber.svg?style=flat" alt="npm">
<img src="https://travis-ci.org/oskarhane/suber.svg?branch=master" alt="travis">

Why Suber?

  • Helpful: Decouples your application modules / components which improves testability
  • Tiny: weighs ≈ 1kb gzipped
  • Compatible: Works with popular side effects handling middlewares like redux-saga and redux-observable
  • Extensible: Easily create middleware to extend it
  • No dependencies: No dependencies, it's less than 100 lines of code
  • Framework bindings: See react-suber for React binding and preact-suber for Preact binding

Usage

Install:
yarn add suber
# or
npm install suber --save

Regular usage:
// Import
import { createBus } from 'suber'

const bus = createBus()

// Listen for a message until manual unsubscription
const unsub = bus.take('MY_CHANNEL', (msg) => {
  console.log(msg.title)
})
// or just one (unsubscribes automatically)
bus.one('MY_CHANNEL', (msg) => {
  console.log(msg.title)
})

// Send a message / data
bus.send('MY_CHANNEL', { title: 'Hello World' })

Usage in combination with Redux:
// app.js
// App init file
import { createBus, createReduxMiddleware } from 'suber'
import { createStore, applyMiddleware } from 'redux'

// Init
const bus = createBus()
const mw = createReduxMiddleware(bus)

// Next line enables sending actions from Redux into suber
const store = createStore(
  yourApp,
  applyMiddleware(mw) // combine with your other middlewares
)

// Send everything from suber into Redux
bus.applyMiddleware((_) => (channel, message, source) => {
  // No loop-backs
  if (source === 'redux') return
  // Send to Redux with the channel as the action type
  store.dispatch({...message, type: channel})
})

// Imagine you have redux-saga setup listening for actions
// with the type: 'GET_USER' and dispatches a new action with
// type 'GOT_USER_RESPONSE_' + id, when response arrives from API.

// ----

// component.js
// React component or anything that has been given access to the suber bus
// either via suber bindings like 'react-suber' or 'preact-suber'
// or through a jsx property.

// Listen for a single message on that channel
bus.one('GOT_USER_RESPONSE_1', (data) => {
  console.log(data)
})

// Send message to redux-saga to fetch user with id = 1
bus.send('GET_USER', { id: 1 })

Use cases

Regular

It can be used as the main eventbus / pubsub in an application to keep components separated without any dependencies (except for suber) and be able to communicate.
For code testability it's better to have all side effect handlers listening and acting on messages on a bus rather than to have async / ajax calls directly in the components. With subers Redux middleware compability, great middlewares like redux-observable and redux-saga can be used to handle the side effects.
On the other hand, creating a suber middleware is super easy so having direct fetch calls in a self-made middleware is often enough for smaller applications.

In combination with Redux

Redux is great for shared application state handling, but often too much state are stored in Redux.
Application state that is not shared should not go into Redux, it should go directly, and only into, the component that needs it.
This is where suber comes in.
Setup your existing Redux middleware side-effect handler (like redux-saga, thunks or redux-observable) to listen for certain action types (GET_USER in the below example) and pass suber to your component (or have a container component handle the calls and pass the response as props to it). Your component can now super easy with
bus.one('GOT_USER_1', (r) => this.setState({user: r.user}))
bus.send('GET_USER', {id: 1})

do API calls and set the component state so component can update the DOM can accordingly. Now all code with side effects is in the same place no matter if the endpoint for the response is Redux or single component state.

API

Factory

Methods

Utility functions

Factory

Factories are functions that returns you the bus.

createBus()

Creates and returns a new bus instance. This is NOT a global singleton anymore.

Methods

These are methods that are attached to the bus instance.

take(channel, fn, filterFn)

Sets up a listener on the bus and calls fn every time a message with a matching channel arrives.

Arguments

  • channel: String What channel to listen on the bus.
  • fn: Function(message) The function to call when a message on the channel arrives.
  • filterFn: Function(message) An optional filter function that can be used to just listen for a specific kind of data
on the channel. See test file for example.

Returns unsubscribe: Function Call this function to unsubscribe this fn on the channel.

one(channel, fn, filterFn)

Sets up a listener on the bus and calls fn one time time once a message with a matching channel arrives. Just like take above but with automatic unsubscription after the first message.

send(channel, message, source)

Send a message on a channel on the bus.

Arguments

  • channel: String The channel to send the message on.
  • message: any The message, can be of any data type.
  • source: String Optional argument to specify the message source.
Best used when applyMiddleware and createReduxMiddleware both are specified to avoid loop-backs.

Returns void

self(channel, message, fn)

Send a message on a channel and expect the receiver of the message to reply back to you. A property (named $$responseChannel) is automatically added to the message for the subscriber to respond on. See tests file for an example.

Arguments

  • channel: String The channel to send the message on.
  • message: any The message, can be of any data type.
  • fn: Function Function to be called with the response from the subscriber.

Returns void

reset()

Removes all subscribers on all channels.

Arguments

No arguments

Returns void

applyMiddleware(fn)

Add middleware to Suber. All messages on all channels gets passed to the middleware. Function1 are called when applying the middleware anf gives access to the send method the the bus. The originObject should only be used when a middleware is used to send all message into Redux, and a Redux middleware created with createReduxMiddleware in combination. This stops actions from being sent in an infinite loop. See test files for example usages. Function2 are called on every message passing through the bus.

Arguments

  • fn: Function1(send, originObject) => Function2(channel, message, source) The function to be called with every message on the bus.

Returns void

resetMiddlewares()

Removes all active all middlewares.

Arguments

No arguments

Returns void

applyReduxMiddleware(rmw)

Add a Redux middleware to Suber. This way the excellent Redux middlewares for handling side effects like redux-saga and redux-observable can be used without Redux. The params for store.getState and next are unsuable, so middlewares that rely on these will not be compatible since Suber isn't about state / reducers.

Arguments

  • rmw: Function() The Redux middleware with (store) => (next) => (action) => signature.

Returns void

Utility functions

Functions to configure or extend the bus.

createReduxMiddleware(bus)

Creates a function that has the redux middleware signature. This function repeats everything on the Redux bus into the passed in Suber bus. Redux action types are mapped to be suber channels.
redux.action = {
  type: 'MY_ACTION',
  some: 'data',
  more: 'data'
}
// becomes
suber.send(redux.action.type, redux.action, 'redux')

Arguments

  • bus: Object A Suber bus instance gotten from createBus().

Returns Function to be passed into Redux's applyMiddleware

Development setup

yarn
npm run dev