Awesome
USub
lite version
tiny version
This javascript library provides utility functions for handling observables, signals, and asynchronous data streams across various reactive programming libraries. It supports flexible customization to integrate with different libraries, ensuring seamless subscription management and automatic cleanup.
Table of Contents
Installation
yarn: yarn add usub
npm: npm i usub
es: https://unpkg.com/usub?module
To use this utility, simply import it into your project:
import { is, api, sub, get } from "usub";
lite
The lite utility does not contain subscribe, and set method for observ-* patterns.
cdn: https://unpkg.com/usub/lite
es: https://unpkg.com/usub/lite?module
Lite Installation
To use lite version, simply import it into your project:
import { is, api, sub, get } from "usub/lite";
tiny
The tiny utility does not contain subscribe, set method for observ-* patterns and async iterable support.
cdn: https://unpkg.com/usub/tiny
es: https://unpkg.com/usub/tiny?module
Tiny Installation
To use tiny version, simply import it into your project:
import { is, api, sub, get } from "usub/tiny";
Usage
Basic Setup
The library exports four primary functions:
is
: Checks if a value is considered an observable or reactive signal.api
: Provides utility functions that can be customized to work with different reactive libraries.sub
: Subscribes to an observable or other async/reactive patterns.get
: get function to retrieve reactive data
Subscribing to Observables
The sub function is used to subscribe to an observable or other async patterns. It handles various types of asynchronous inputs like promises, async iterables, and functions.
const observable = {
subscribe: (next, error, complete) => {
next(1);
next(2);
next(3);
complete();
return {
unsubscribe: () => console.log("Unsubscribed"),
};
},
};
const unsubscribe = sub(observable, console.log, console.error, () =>
console.log("Complete")
);
Checking if an Object is Observable
Use the is function to check if a value is considered an observable by the library:
const observable = {
subscribe: () => {},
};
console.log(is(observable)); // Output: true
Promise
{
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
sub(promise,
(value) => console.log("Resolved value:", value),
(error) => console.error("Error:", error),
() => console.log("Promise complete")
);
}
Customizing for Your Reactive Library
The library is designed to be easily customized for different reactive programming libraries. To integrate with your preferred library, you can customize the following api functions:
API Overview
- api.any(target)(next, err, complete)
api.any is a placeholder within the api object that can be used to represent or handle any observable-like object.
Purpose
The primary purpose of api.any is to provide a centralized place for handling various types of observable objects, whether they are standard observables, promises, or custom implementations. This placeholder is particularly useful for abstraction and uniform handling of different observable sources in a consistent manner.
Default Behavior
By default, api.any is set to undefined. This means that if it is not explicitly assigned a function or object, it will not provide any observable functionality. You need to assign it a specific function or observable to make it work.
Usage
You can customize api.any to handle your specific observable implementations. For example, you might set api.any to a function that processes different observable types or provides default behavior for handling observable subscriptions and notifications.
- api.effect(f)
Sets up the effect execution method. This function is where you define how to apply effects in your reactive library (e.g., createEffect in Solid.js, effect in Preact Signals).
- api.is(v)
Defines how to check if a value is a signal or observable. This is where you identify reactive signals from your library (e.g., checking if a value is an instance of Signal).
- api.get(v)
Specifies how to retrieve the current value from a signal or observable. This function is where you define how to extract the current value from your reactive signal (e.g., v?.value or v?.()).
- api.cleanup()
Provides a function to handle cleanup logic. This can be used to define any custom cleanup behavior required when a subscription is no longer needed.
Example API Customization
Any source
export let v =
(v, cb = []) =>
(c) =>
c === void 0
? v
: c.call
? cb.splice.bind(cb, cb.push(c) - 1, 1, 0)
: cb.map((f) => f && f((v = c)));
api.any = (target) => (next, error, complete) => target?.((v) => next(v));
const num = v(42);
let off = sub(num, console.log);
num(20);
num(3);
off();
num(30);
Solidjs
const { createSignal, createEffect, cleanup } = require("solid-js");
api.effect = createEffect;
api.is = (v) => v?.name?.includes("readSignal");
api.get = (v) => v?.();
api.cleanup = cleanup; //optional
const [val, setVal] = createSignal(0);
sub(val, console.log);
setVal(10);
setVal(20);
Preact Signals
const { effect, signal, Signal } = require("@preact/signals-core");
api.effect = effect;
api.is = (v) => v instanceof Signal;
api.get = (v) => v?.value;
const val = signal(0);
const stop = sub(val, (v) => {
console.log(v);
});
val.value = 10;
val.value = 20;
stop();
val.value = 30;
USignal
const { effect, signal, Signal } = require("usignal");
api.effect = effect;
api.is = (v) => v instanceof Signal;
api.get = (v) => v?.value;
const val = signal(0);
const stop = sub(val, (v) => {
console.log(v);
});
val.value = 10;
val.value = 20;
stop();
val.value = 30;
@webreflection/signal
const { effect, signal, Signal } = require("@webreflection/signal");
api.effect = effect;
api.is = (v) => v instanceof Signal;
api.get = (v) => v?.value;
const val = signal(0);
const stop = sub(val, (v) => {
console.log(v);
});
val.value = 10;
val.value = 20;
stop();
val.value = 30;
ULive
const { effect, signal } = require("ulive");
api.effect = effect;
api.is = (v) => v?.peek;
api.get = (v) => v?.value;
const val = signal(0);
const stop = sub(val, (v) => {
console.log(v);
});
val.value = 10;
val.value = 20;
stop();
val.value = 30;
RxJS
const { Subject } = require("rxjs");
const subject = new Subject();
let arr = [];
const unsub = sub(subject,
(v) => arr.push(v),
(err) => arr.push(err),
() => arr.push("end")
);
subject.next(1);
subject.next(2);
subject.complete();
console.log(arr);
unsub();
Async Iterable
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 5)
return new Promise((ok) =>
setTimeout(() => ok({ value: this.i++, done: false }), 10)
);
return new Promise((ok) => ok({ done: true }));
},
};
},
};
sub(asyncIterable, console.log, console.error, () => console.log("end"));
Finalization and Cleanup
The library uses FinalizationRegistry to automatically clean up subscriptions when objects are garbage collected. This helps prevent memory leaks by ensuring that subscriptions are properly terminated when no longer needed.
License
This library is provided "as-is" under the MIT license. Feel free to use, modify, and distribute it in your projects.