suub
Version:
A simple pub/sub written in Typescript
315 lines (218 loc) • 8.39 kB
Markdown
<p align="center">
<img src="https://raw.githubusercontent.com/etienne-dldc/suub/main/design/logo.png" width="597" alt="suub logo">
</p>
# 📫 Suub
> A simple pub/sub written in Typescript
## Gist
```ts
import { Suub } from 'suub';
const mySub = Suub.createSubscription<number>();
const unsub = mySub.subscribe((num) => {
console.log('num: ' + num);
});
mySub.emit(45); // num: 45
unsub();
```
## Guide
### Creating a Subscription
To create a `Subscription` you need to import the `Suub.createSubscription` function and call it.
```ts
import { Suub } from 'suub';
const subscription = Suub.createSubscription();
```
If you use TypeScript, you need to pass a type parameter to the `Suub.createSubscription` function to define the type of the value associated with the subscription.
```ts
import { Suub } from 'suub';
const numSubscription = Suub.createSubscription<number>();
```
If you don't want your subscription not to emit any value, you can use the `Suub.createVoidSubscription` function.
```ts
import { Suub } from 'suub';
const voidSubscription = Suub.createVoidSubscription();
```
### Subscribe and Unsubscribe
You have two ways to `subscribe` / `unsubscribe`.
- Using the reference of the callback function
```ts
const callback = () => {
/*...*/
};
subscription.subscribe(callback);
// later
subscription.unsubscribe(callback);
```
- Using a SubId (a string)
```ts
subscription.subscribeById('mySubId', () => {
/*...*/
});
// later
subscription.unsubscribeById('mySubId');
```
In both case the `subscribe[ById]` return a function that will unsubscribe:
```ts
const unsub = subscription.subscribe(/*...*/);
// later
unsub();
```
### Emitting value
To emit a value and trigger all subscribed `callback` you need to call the `emit` method.
```ts
subscription.emit(42);
// for void subscription you don't need to pass any value
subscription.emit();
```
### OnUnsubscribe
The `subscribe[ById]` methods accept a optional function after the callback, this function will be called when this callback you are subscribing is unsubscribed.
```ts
subscription.subscribe(
() => {
/* ... */
},
() => {
console.log('Unsubscribed !');
}
);
// or with a subId
subscription.subscribeById(
'mySub',
() => {
/* ... */
},
() => {
console.log('Unsubscribed !');
}
);
```
### Unsubscribing all subscriptions
You can call `unsubscribeAll` method on a subscription to remove all callback. This will also trigger the `onUnsubscribe` if any.
```ts
subscription.unsubscribeAll();
```
### `Subscription` options
The `Subscription.create` (or `Subscription.createVoid`) functions accept an option object as parameter (all properties are optional):
```ts
const sub = Subscription.create({
onFirstSubscription: () => {},
onLastUnsubscribe: () => {},
onDestroy: () => {},
maxSubscriptionCount: 10000,
maxRecursiveEmit: 1000,
maxUnsubscribeAllLoop: 1000,
});
```
#### `onFirstSubscription`
> A function called when the number of subscribers goes from `0` to `1`
#### `onLastUnsubscribe`
> A function called when the number of subscribers goes from `1` to `0`
#### `onDestroy`
> A function called when the `destroy` method is called. Note that during this call the `Subscription` is already destroyed and you can't call `emit` or `subscribe` anymore.
#### `maxSubscriptionCount`
> A number to limit the maximum number of simultaneous subscriptions (default is `10000`). This limit exist to detect infinit subscription loop.
#### `maxRecursiveEmit`
> A number to limit the maximum recursive call of `emit` (default is `1000`). This limit exist to detect infinite loop where you `emit` in a `callback`.
#### `maxUnsubscribeAllLoop`
> A number to limit the maximum recursive call of `subscribe` inside a `onUnsubscribe` callback (default is `1000`).
### Testing if a callback / subId is subscribed
The `isSubscribed[ById]` methods let you test whether or not a callback / subId is currently subscribed
```ts
subscription.isSubscribed(myCallback); // <- boolean
subscription.isSubscribedById('my-sub-id'); // <- boolean
```
### Reading the number of active Subscriptions
You can call the `size` method to get the number of subscriptions.
```ts
subscription.size();
```
### Destroying a Subscription
You can call the `destroy` method to destroy a subscription. This will unsubscribe all callback and call the `onDestroy` option if any.
```ts
subscription.destroy();
```
Once destroyed, calling `emit` or `subscribe[ById]` will throw an error. You can still call the other methods but they will have no effect.
You can check if a subscription is destroyed by calling the `isDestroyed` method.
```ts
subscription.isDestroyed(); // <- boolean
```
## Channels
```
// TODO
```
## Some precisions
#### Callback are called in the order they are subscribed.
#### If you re-subscribe the same callback or id it will not re-do a subscription but instead move the subscription to the end.
In other words, calling `subscribe` on an already subscribed callback or subId will not make the callback called twice. But it will move the callback at the end of the subscription list.
In the case of a subId, the callback will be replaced by the new one.
#### If you call `unsubscribe` in a callback it will have effect immediatly.
If the callback you unsubscribe is supposed to run after the current callback, it will not be called.
#### If you `subscribe` in a callback it will not be called immediatly.
But it will be in the next `emit`.
#### If you `emit()` in a callback it will defer the call to after the current emit is done.
#### If you `subscribe` / `unsubscribe` / `emit` in an `onUnsubscribed` it will behave the same as if it was in the callback itself
#### Calling `destroy` will unsubscribe all callback and call the `onUnsubscribe` if any
In these `onUnsubscribe` callback the subscription is considered destroyed so you can't call `emit` or `subscribe` anymore.
#### Calling `destroy` on a destroyed subscription will have no effect
This is a no-op, it will not call `onDestroy` again.
#### The subscription is already considered destroyed when `onDestroy` is called
This means that you can't call `emit` or `subscribe` in the `onDestroy` callback and that `isDestroyed` will return `true` in the `onDestroy` callback.
## Examples
Take a look at the [Examples folder](https://github.com/etienne-dldc/suub/tree/master/examples).
## API
```ts
export type Unsubscribe = () => void;
export type OnUnsubscribed = () => void;
export type SubscriptionCallback<T> = (value: T) => void;
export type VoidSubscriptionCallback = () => void;
export type UnsubscribeAllMethod = () => void;
export interface SubscribeMethod<T> {
(callback: SubscriptionCallback<T>, onUnsubscribe?: OnUnsubscribed): Unsubscribe;
(subId: string, callback: SubscriptionCallback<T>, onUnsubscribe?: OnUnsubscribed): Unsubscribe;
}
export interface VoidSubscribeMethod {
(callback: VoidSubscriptionCallback, onUnsubscribe?: OnUnsubscribed): Unsubscribe;
(subId: string, callback: VoidSubscriptionCallback, onUnsubscribe?: OnUnsubscribed): Unsubscribe;
}
export interface IsSubscribedMethod<T> {
(subId: string): boolean;
(callback: SubscriptionCallback<T>): boolean;
}
export interface UnsubscribeMethod<T> {
(subId: string): void;
(callback: SubscriptionCallback<T>): void;
}
export interface VoidIsSubscribedMethod {
(subId: string): boolean;
(callback: VoidSubscriptionCallback): boolean;
}
export interface VoidUnsubscribeMethod {
(subId: string): void;
(callback: VoidSubscriptionCallback): void;
}
export interface ISubscription<T> {
subscribe: SubscribeMethod<T>;
unsubscribe: UnsubscribeMethod<T>;
unsubscribeAll: UnsubscribeAllMethod;
isSubscribed: IsSubscribedMethod<T>;
size: () => number;
emit: (newValue: T) => void;
destroy: () => void;
isDestroyed: () => boolean;
}
export interface IVoidSubscription {
subscribe: VoidSubscribeMethod;
unsubscribe: VoidUnsubscribeMethod;
unsubscribeAll: UnsubscribeAllMethod;
isSubscribed: VoidIsSubscribedMethod;
size: () => number;
emit: () => void;
destroy: () => void;
isDestroyed: () => boolean;
}
export interface ISubscriptionOptions {
onFirstSubscription?: () => void;
onLastUnsubscribe?: () => void;
onDestroy?: () => void;
maxSubscriptionCount?: number;
maxRecursiveEmit?: number;
}
```