Home

Awesome

USub

tests Version Badge size Badge size

lite version

Badge size Badge size

tiny version

Badge size Badge size

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

cdn: https://unpkg.com/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:

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 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.

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).

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).

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?.()).

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.

Thanks and Inspiration