UNPKG

abortable-rx

Version:

Drop-in replacements for RxJS Observable methods and operators that work with AbortSignal

109 lines 3.95 kB
import { from, Observable, Subscription } from 'rxjs'; import { concatMap as rxConcatMap, mergeMap as rxMergeMap, switchMap as rxSwitchMap } from 'rxjs/operators'; const createAbortError = () => { const error = new Error('Aborted'); error.name = 'AbortError'; return error; }; /** * Creates an Observable just like RxJS `create`, but exposes an AbortSignal in addition to the subscriber */ export const create = (subscribe) => new Observable(subscriber => { const abortController = new AbortController(); const subscription = new Subscription(); const teardown = subscribe && subscribe(subscriber, abortController.signal); subscription.add(teardown); subscription.add(() => abortController.abort()); return subscription; }); /** * Easiest way to wrap an abortable async function into a Promise. * The factory is called every time the Observable is subscribed to, and the AbortSignal is aborted on unsubscription. */ export const defer = (factory) => create((subscriber, signal) => from(factory(signal)).subscribe(subscriber)); /** * Returns a Promise that resolves with the last emission of the given Observable, * rejects if the Observable errors or rejects with an `AbortError` when the AbortSignal is aborted. */ export const toPromise = (observable, signal) => new Promise((resolve, reject) => { if (signal && signal.aborted) { reject(createAbortError()); return; } let subscription; const listener = () => { subscription.unsubscribe(); reject(createAbortError()); }; const cleanup = () => { if (signal) { signal.removeEventListener('abort', listener); } }; let value; subscription = observable.subscribe(val => { value = val; }, err => { cleanup(); reject(err); }, () => { cleanup(); resolve(value); }); if (signal) { signal.addEventListener('abort', listener, { once: true }); } }); /** * Calls `next` for every emission and returns a Promise that resolves when the Observable completed, rejects if the * Observable errors or rejects with an `AbortError` when the AbortSignal is aborted. */ export const forEach = (source, next, signal) => new Promise((resolve, reject) => { if (signal && signal.aborted) { reject(createAbortError()); return; } let subscription; const listener = () => { subscription.unsubscribe(); reject(createAbortError()); }; const cleanup = () => { if (signal) { signal.removeEventListener('abort', listener); } }; subscription = source.subscribe(value => { try { next(value); } catch (err) { reject(err); if (subscription) { subscription.unsubscribe(); } } }, err => { cleanup(); reject(err); }, () => { cleanup(); resolve(); }); if (signal) { signal.addEventListener('abort', listener, { once: true }); } }); /** * Like RxJS `switchMap`, but passes an AbortSignal that is aborted when the source emits another element. */ export const switchMap = (project) => source => source.pipe(rxSwitchMap((value, index) => defer(abortSignal => project(value, index, abortSignal)))); /** * Like RxJS `concatMap`, but passes an AbortSignal that is aborted when the returned Observable is unsubscribed from. */ export const concatMap = (project) => source => source.pipe(rxConcatMap((value, index) => defer(abortSignal => project(value, index, abortSignal)))); /** * Like RxJS `mergeMap`, but passes an AbortSignal that is aborted when the returned Observable is unsubscribed from. */ export const mergeMap = (project) => source => source.pipe(rxMergeMap((value, index) => defer(abortSignal => project(value, index, abortSignal)))); //# sourceMappingURL=index.js.map