https://github.com/dldc-packages/pubsub
📫 A simple pub/sub written in Typescript
https://github.com/dldc-packages/pubsub
pubsub subscription typescript
Last synced: 6 months ago
JSON representation
📫 A simple pub/sub written in Typescript
- Host: GitHub
- URL: https://github.com/dldc-packages/pubsub
- Owner: dldc-packages
- License: mit
- Created: 2019-10-20T11:47:26.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2024-05-04T16:24:29.000Z (about 2 years ago)
- Last Synced: 2025-03-17T21:44:09.717Z (over 1 year ago)
- Topics: pubsub, subscription, typescript
- Language: TypeScript
- Homepage:
- Size: 2.28 MB
- Stars: 14
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 📫 PubSub
> A pub/sub library written in TypeScript
```
npm install @dldc/pubsub
```
## Gist
```ts
import { createSubscription } from "@dldc/pubsub";
const mySub = createSubscription();
const unsub = mySub.subscribe((num) => {
console.log("num: " + num);
});
mySub.emit(45); // num: 45
unsub(); // Unsubscribe the callback
```
## Guide
### Creating a Subscription
To create a `Subscription` you need to import the `createSubscription` function
and call it.
```ts
import { createSubscription } from "@dldc/pubsub";
const subscription = createSubscription();
```
If you use TypeScript, you need to pass a type parameter to the
`createSubscription` function to define the type of the value associated with
the subscription.
```ts
import { createVoidSubscription } from "@dldc/pubsub";
const numSubscription = createSubscription();
```
If you don't want your subscription to emit any value, you can use the
`createVoidSubscription` function.
```ts
import { createVoidSubscription } from "@dldc/pubsub";
const voidSubscription = 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
voidSubscription.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 `createSubscription` (or `createVoidSubscription`) 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
```
## Some precisions
#### Callback are called in the order they are subscribed.
```ts
import { createSubscription } from "@dldc/pubsub";
const subscription = createSubscription();
subscription.subscribe((value) => console.log("First callback: " + value));
subscription.subscribe((value) => console.log("Second callback: " + value));
subscription.emit(42);
// Output:
// First callback: 42
// Second callback: 42
```
#### 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.
```ts
const callback = (value: number) => console.log("Callback: " + value);
const otherCallback = (value: number) =>
console.log("Other callback: " + value);
subscription.subscribe(callback);
subscription.subscribe(otherCallback);
subscription.subscribe(callback); // Moves the callback to the end
subscription.emit(42);
// Output:
// Other callback: 42
// Callback: 42
```
#### 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.
```ts
const cb1 = () => {
console.log("Callback 1");
subscription.unsubscribe(cb2);
};
const cb2 = () => {
console.log("Callback 2");
};
subscription.subscribe(cb1);
subscription.subscribe(cb2);
subscription.emit(42);
// Output:
// Callback 1
```
#### If you `subscribe` in a callback it will not be called immediatly.
But it will be in the next `emit`.
```ts
subscription.subscribe((value) => {
console.log("First callback: " + value);
subscription.subscribe((v) => console.log("New callback: " + v));
});
subscription.emit(42);
subscription.emit(43);
// Output:
// First callback: 42
// First callback: 43
// New callback: 43
```
#### If you `emit()` in a callback it will defer the call to after the current emit is done.
```ts
const sb1 = (value: number) => {
console.log("First callback: " + value);
if (value === 42) {
subscription.emit(43);
}
};
const cb2 = (value: number) => {
console.log("Second callback: " + value);
};
subscription.subscribe(sb1);
subscription.subscribe(cb2);
subscription.emit(42);
// Output:
// First callback: 42
// Second callback: 42
// First callback: 43
// Second callback: 43
```
#### If you `subscribe` / `unsubscribe` / `emit` in an `onUnsubscribed` it will behave in the same way 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.
```ts
subscription.subscribe(
() => console.log("Callback"),
() => console.log("Unsubscribed"),
);
subscription.destroy();
// Output:
// Unsubscribed
```
#### Calling `destroy` on a destroyed subscription will have no effect
This is a no-op, it will not call `onDestroy` again.
```ts
subscription.destroy();
subscription.destroy(); // No effect
```
#### 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.
```ts
const subscription = createSubscription({
onDestroy: () => {
console.log("Destroyed");
console.log("Is destroyed: " + subscription.isDestroyed());
},
});
subscription.destroy();
// Output:
// Destroyed
// Is destroyed: true
```
## Scheduler [ADVANCED]
At the core of the `Subscription` is a scheduler that will manage the different
callbacks and their order of execution. If you need a single subscription or
event multiple that don't interact with each other, you don't need to know about
the scheduler. But if you need for example to subscribe to a subscription in the
callback of another subscription then keep reading.
### Resuse the same scheduler for multiple subscriptions
You can create a `Scheduler` unsing the `createScheduler` function. You can then
pass this scheduler as the first option of the `createSubscription` and
`createVoidSubscription` functions.
```ts
import { createScheduler, createSubscription } from "@dldc/pubsub";
const scheduler = createScheduler();
const sub1 = createSubscription(scheduler);
const sub2 = createSubscription(scheduler);
```
Note that the `createScheduler` function accept the same options as the
`createSubscription` function. When you pass a scheduler to create a
subscription, you can also pass a second argument to specify a
`onFirstSubscription` and `onLastUnsubscribe` function specific to this
subscription.
```ts
import { createScheduler, createSubscription } from "@dldc/pubsub";
const scheduler = createScheduler();
const sub1 = createSubscription(scheduler, {
onFirstSubscription: () => {
console.log("First subscription");
},
onLastUnsubscribe: () => {
console.log("Last unsubscribe");
},
});
```
### Destrying a scheduler
Note that when you destroy a scheduler, all subscriptions that use this
scheduler will be destroyed as well. Calling `.destroy()` on a subscription will
actually call `.destroy()` on the scheduler.
## API
```ts
export type Unsubscribe = () => void;
export type OnUnsubscribed = () => void;
export type SubscriptionCallback = (value: T) => void;
export type VoidSubscriptionCallback = () => void;
export type UnsubscribeAllMethod = () => void;
export interface SubscribeMethod {
(
callback: SubscriptionCallback,
onUnsubscribe?: OnUnsubscribed,
): Unsubscribe;
(
subId: string,
callback: SubscriptionCallback,
onUnsubscribe?: OnUnsubscribed,
): Unsubscribe;
}
export interface VoidSubscribeMethod {
(
callback: VoidSubscriptionCallback,
onUnsubscribe?: OnUnsubscribed,
): Unsubscribe;
(
subId: string,
callback: VoidSubscriptionCallback,
onUnsubscribe?: OnUnsubscribed,
): Unsubscribe;
}
export interface IsSubscribedMethod {
(subId: string): boolean;
(callback: SubscriptionCallback): boolean;
}
export interface UnsubscribeMethod {
(subId: string): void;
(callback: SubscriptionCallback): void;
}
export interface VoidIsSubscribedMethod {
(subId: string): boolean;
(callback: VoidSubscriptionCallback): boolean;
}
export interface VoidUnsubscribeMethod {
(subId: string): void;
(callback: VoidSubscriptionCallback): void;
}
export interface ISubscription {
subscribe: SubscribeMethod;
unsubscribe: UnsubscribeMethod;
unsubscribeAll: UnsubscribeAllMethod;
isSubscribed: IsSubscribedMethod;
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;
}
```