/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ObserverNext, ObserverStart, SloppyObserver, SubscriberFunction, Subscription, SubscriptionObserver } from './index' /** * Observable provides an interface for an event system which can emit multiple values, * is lazily evaluated and can be unsubscribed from. The laws of observable * are: * * - Observable is not async, but can be easily used in an async way * - Observable puts composability above all else (see utils below) * - Observable is not evaluated until someone subscribes * - Observable is no longer evaluated after someone unsubscribes * * @example Event listener as observable, emitting events * * mouseClicks = new Observable(({next} => { * window.addEventListener('click', next) * return () => { window.removeEventListener('click', next) } * }) * * let unsubscribe = mouseClicks.subscribe(({ preventDefault }) => preventDefault()) */ export default class Observable { public subscribe: (observer: SloppyObserver) => Subscription public constructor(subscribe: SubscriberFunction) { // subscribe wraps the which only accepts proper // objects, and instead provides a subscribe function which // accepts , allowing for omitting properties in a // subscriber object, without causing failures in the subscriberCallback this.subscribe = ( sloppyObserver, error?: (errorValue: Error) => void, complete?: (value: T) => void ): Subscription => { let start: ObserverStart | undefined let next: ObserverNext | undefined if (typeof sloppyObserver === 'function') { next = sloppyObserver } else { start = sloppyObserver.start next = sloppyObserver.next error = sloppyObserver.error complete = sloppyObserver.complete } let cleanup: () => void let closed = false const wrapClosed = (fn?: (v?: T) => void): (() => void) => ( v?: T ): boolean | void => closed || (fn && fn(v)) const unsubscribe = wrapClosed( (): void => { closed = true cleanup && cleanup() } ) const wrapUnsubscribe = (fn?: (v?: any) => void): (() => void) => ( v?: any ): void => { unsubscribe() typeof fn === 'function' && fn(v) } error = wrapClosed(wrapUnsubscribe(error)) const wrapTry = (fn: (v: T) => void): (() => void) => (v?: T): void => { try { fn(v!) } catch (e) { typeof error === 'function' && error(e) } } complete = wrapClosed(wrapTry(wrapUnsubscribe(complete))) next = wrapClosed(wrapTry(next!)) const subscription: Subscription = { get closed(): boolean { return closed }, unsubscribe } start && start(subscription) if (closed) { return subscription } wrapTry( (): void => { const observer: SubscriptionObserver = { closed, complete: complete!, error: error!, next: next! } const wSub = subscribe(observer) as Subscription cleanup = wSub as any if (wSub && typeof wSub.unsubscribe === 'function') { cleanup = wSub.unsubscribe } } )() return subscription } } }