suspenders-js
Version:
Asynchronous programming library utilizing coroutines, functional reactive programming and structured concurrency.
714 lines • 23 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SharedEventFlow = exports.StateSubject = exports.EventSubject = exports.SharedStateFlow = exports.flowOfValues = exports.flowOf = exports.Flow = void 0;
const Channel_1 = require("./Channel");
const Errors_1 = require("./Errors");
const ObserverFunction_1 = require("./ObserverFunction");
const Scope_1 = require("./Scope");
/**
* Abstract class for emitting multiple values. A cold flow is doesn't start producing values until
* it is yield in a coroutine with .collect() or .collectLatest(). Sharing a cold flow with multiple
* coroutines requires converting it into a SharedEventSubject or SharedStateSubject. Hot flows
* are EventSubjects and StateSubjects.
*/
class Flow {
/**
* Converts values emitted using mapper function.
* @param {(value) => R} mapper
* @returns {Flow<R>}
*/
map(mapper) {
return new MapFlow(this, mapper);
}
/**
* Values that don't return true from predicate function are not emitted downstream.
* @param predicate
*/
filter(predicate) {
return new FilterFlow(this, predicate);
}
/**
* Runs binder on each emitted value and combines outputs into a single flow.
* @param binder
*/
mergeMap(binder) {
return new MergeMapFlow(this, binder);
}
/**
* Runs f on each value but doesn't change values emitted in downstream flow.
* @param f
*/
onEach(f) {
return new OnEachFlow(this, f);
}
/**
* Consumes values in upstream Flow. Shares values with downstream flow. Replays last value
* emitted on new observers.
* @returns {Flow<T>}
*/
sharedState() {
return new SharedStateFlow(this);
}
/**
* Consumes values in upstream Flow. Shares values with downstream flow.
* @returns {Flow<T>}
*/
sharedEvent() {
return new SharedEventFlow(this);
}
/**
* Collects Flow in scope, ignoring emitted values.
* @param scope
*/
launchIn(scope) {
const that = this;
scope.launch(function* () {
yield that.collect(() => { });
});
}
/**
* Consumes Flow in scope. Runs collector function on emitted values.
* @param {(value: T) => void} collector
* @returns {Suspender<void>}
*/
collect(collector) {
return (resultCallback) => {
const observerFunction = new ObserverFunction_1.ObserverFunction(collector, () => { resultCallback({ value: undefined }); }, (error) => { resultCallback({ tag: `error`, error }); });
this.addObserver(observerFunction);
return () => {
this.removeObserver(observerFunction);
};
};
}
/**
* Consumes Flow in scope. Runs collector coroutine on emitted values. Cancels previously started
* coroutine if it has not completed.
* @param {(value: T) => CoroutineFactory<void>} coroutineFactory
*/
collectLatest(coroutineFactory) {
return (resultCallback) => {
let cancelFunction;
const scope = new Scope_1.Scope({ errorCallback: (error) => {
resultCallback({ tag: `error`, error });
} });
const observer = new ObserverFunction_1.ObserverFunction((value) => {
if (cancelFunction !== undefined) {
cancelFunction();
}
cancelFunction = scope.launch(coroutineFactory(value));
}, () => { resultCallback({ value: undefined }); }, (error) => { resultCallback({ tag: `error`, error }); });
this.addObserver(observer);
return () => {
this.removeObserver(observer);
if (cancelFunction !== undefined) {
cancelFunction();
}
};
};
}
/**
* Runs coroutine on each value and emits values through observer. Waits for each
* coroutine to complete before starting the next. Because there is no backpressure with flow, use
* care to make sure that emitted values don't buffer uncontrollably.
* @param transformer
*/
transform(transformer) {
return new TransformFlow(this, transformer);
}
/**
* Errors thrown upstream are caught and passed into coroutine factory. Resumes sending values
* emitted to observer.
* @param factory
*/
catch(factory) {
return new TransformCatch(this, factory);
}
/**
* Runs a coroutine on each value and emits values through observer. Cancels previous coroutine if
* it has not completed.
* @param transformer
*/
transformLatest(transformer) {
return new TransformLatestFlow(this, transformer);
}
}
exports.Flow = Flow;
class MapFlow extends Flow {
constructor(_flow, _mapper) {
super();
this._flow = _flow;
this._mapper = _mapper;
this._hasCompleted = false;
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
}
emit(value) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._observer.emit(this._mapper(value));
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.complete();
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.error(error);
}
}
class FilterFlow extends Flow {
constructor(_flow, _predicate) {
super();
this._flow = _flow;
this._predicate = _predicate;
this._hasCompleted = false;
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
}
emit(value) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
if (this._predicate(value)) {
this._observer.emit(value);
}
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.complete();
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.error(error);
}
}
class MergeMapFlow extends Flow {
constructor(_flow, _binder) {
super();
this._flow = _flow;
this._binder = _binder;
this._hasCompleted = false;
this._flows = new Map();
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
}
emit(value) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
const observerFunction = new ObserverFunction_1.ObserverFunction((value) => { this._observer.emit(value); }, () => {
this._flows.delete(observerFunction);
if (this._hasCompleted && this._flows.size === 0) {
this._observer.complete();
}
}, (error) => {
this._flows.delete(observerFunction);
this.error(error);
});
const flow = this._binder(value);
this._flows.set(observerFunction, flow);
flow.addObserver(observerFunction);
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
for (const [observerFunction, flow] of this._flows) {
flow.removeObserver(observerFunction);
}
this._observer.error(error);
}
}
class OnEachFlow extends Flow {
constructor(_flow, _onEach) {
super();
this._flow = _flow;
this._onEach = _onEach;
this._hasCompleted = false;
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
}
emit(value) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._onEach(value);
this._observer.emit(value);
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.complete();
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.error(error);
}
}
class TransformFlow extends Flow {
constructor(_flow, _transformerFactory) {
super();
this._flow = _flow;
this._transformerFactory = _transformerFactory;
this._hasCompleted = false;
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._scope = new Scope_1.Scope({ errorCallback: (error) => {
observer.error(error);
} });
this._receiverChannel = new Channel_1.Channel({ bufferSize: Infinity });
this._observerFunction = new ObserverFunction_1.ObserverFunction((value) => { observer.emit(value); }, () => { if (this._hasCompleted) {
observer.complete();
} }, (error) => { this.error(error); });
const that = this;
this._scope.launch(function* () {
while (!that._hasCompleted) {
yield* this.call(that._transformerFactory(yield* this.suspend(that._receiverChannel.receive), that._observerFunction));
}
});
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
this._scope.cancel();
}
emit(value) {
if (this._receiverChannel === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
// channel buffer is Infinite so we don't check for failure
this._receiverChannel.trySend(value);
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._scope.cancel();
this._observer.error(error);
}
}
class TransformCatch extends Flow {
constructor(_flow, _transformerFactory) {
super();
this._flow = _flow;
this._transformerFactory = _transformerFactory;
this._hasCompleted = false;
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
this._flow.addObserver(this);
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._flow.removeObserver(this);
this._scope?.cancel();
}
emit(value) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._observer.emit(value);
}
complete() {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._observer.complete();
}
error(error) {
if (this._observer === undefined) {
throw new Errors_1.ObserverError();
}
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
this._scope = new Scope_1.Scope({ errorCallback: (error) => {
this._observer?.error(error);
} });
this._scope.launch(this._transformerFactory(error, this._observer));
}
}
class TransformLatestFlow extends Flow {
constructor(_flow, _transformerFactory) {
super();
this._flow = _flow;
this._transformerFactory = _transformerFactory;
this._hasCompleted = false;
this._scope = new Scope_1.Scope({ errorCallback: (error) => {
this._observer.error(error);
} });
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
const downstreamObserver = new ObserverFunction_1.ObserverFunction((value) => { observer.emit(value); }, () => { if (this._hasCompleted) {
observer.complete();
} }, (error) => { this._scope._cancelWithError(error); });
const upstreamObserver = new ObserverFunction_1.ObserverFunction((value) => {
if (this._coroutine !== undefined) {
this._scope._cancelCallbacks.get(this._coroutine)?.call(undefined);
}
this._coroutine = this._transformerFactory(value, downstreamObserver).call(this._scope);
this._scope._resume(this._coroutine, { value: undefined });
}, () => { this._hasCompleted = true; }, (error) => { this._scope._cancelWithError(error); });
this._cancel = () => {
this._flow.removeObserver(upstreamObserver);
if (this._coroutine !== undefined) {
this._scope._cancelCallbacks.get(this._coroutine)?.call(undefined);
}
};
this._flow.addObserver(upstreamObserver);
}
removeObserver(observer) {
if (this._observer === observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._scope.cancel();
}
}
class FlowOf extends Flow {
constructor(_coroutineFactory) {
super();
this._coroutineFactory = _coroutineFactory;
this._scope = new Scope_1.Scope({ errorCallback: (error) => {
this._observer.error(error);
} });
}
addObserver(observer) {
if (this._observer !== undefined) {
throw new Errors_1.FlowConsumedError();
}
this._observer = observer;
const that = this;
this._cancel = this._scope.launch(function* () {
yield* this.call(that._coroutineFactory(observer));
observer.complete();
});
}
removeObserver(observer) {
if (this._observer !== observer) {
throw new Errors_1.FlowRemoveObserverError();
}
this._cancel();
}
}
const flowOf = (factory) => new FlowOf(factory);
exports.flowOf = flowOf;
class FlowOfValues extends Flow {
constructor(...args) {
super();
this._values = args;
}
addObserver(observer) {
for (const value of this._values) {
observer.emit(value);
}
observer.complete();
}
removeObserver() { }
}
const flowOfValues = (...args) => new FlowOfValues(...args);
exports.flowOfValues = flowOfValues;
/**
* Starts observing a upstream flow and shares received values to downstream observers.
* SharedStateFlow replay the last emitted value to new observers.
*/
class SharedStateFlow extends Flow {
constructor(_flow) {
super();
this._flow = _flow;
this._observers = new Set();
this._hasCompleted = false;
this._flow.addObserver(this);
}
addObserver(observer) {
this._observers.add(observer);
if (this._last !== undefined) {
observer.emit(this._last.value);
}
if (this._hasCompleted) {
observer.complete();
}
}
removeObserver(observer) {
if (!this._observers.has(observer)) {
throw new Errors_1.FlowRemoveObserverError();
}
this._observers.delete(observer);
}
emit(value) {
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._last = { value };
for (const observer of this._observers) {
observer.emit(value);
}
}
complete() {
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
for (const observer of this._observers) {
observer.complete();
}
}
error(error) {
for (const observer of this._observers) {
observer?.error(error);
}
}
}
exports.SharedStateFlow = SharedStateFlow;
/**
* EventSubjects update their observers when there is a new event. Previously emitted values are not
* replayed on new observers. To replay the last emitted value, use StateSubject. Subjects are hot
* and can be shared with multipler observers. New flows that observe subjects start cold.
*/
class EventSubject extends Flow {
constructor() {
super(...arguments);
this._observers = new Set();
}
addObserver(observer) {
this._observers.add(observer);
}
removeObserver(observer) {
if (!this._observers.has(observer)) {
throw new Errors_1.FlowRemoveObserverError();
}
this._observers.delete(observer);
}
/**
* Emits a value to the observer.
* @param value
*/
emit(value) {
for (const observer of this._observers) {
observer.emit(value);
}
}
}
exports.EventSubject = EventSubject;
/**
* StateSubject always have a value. When new observers are added, the last emitted value is
* replayed. This is generally used used for hot observables like the mouse position. Subjects are
* hot and can be shared with multipler observers. New flows that observe subjects start cold.
*/
class StateSubject extends Flow {
constructor(value) {
super();
this.value = value;
this._observers = new Set();
}
addObserver(observer) {
this._observers.add(observer);
observer.emit(this.value);
}
removeObserver(observer) {
if (!this._observers.has(observer)) {
throw new Errors_1.FlowRemoveObserverError();
}
this._observers.delete(observer);
}
emit(value) {
this.value = value;
for (const observer of this._observers) {
observer.emit(value);
}
}
get() {
return this.value;
}
}
exports.StateSubject = StateSubject;
/**
* Starts observing a upstream flow and shares received values to downstream observers.
* SharedEventFlow doesn't replay any past emitted values.
*/
class SharedEventFlow extends Flow {
constructor(_flow) {
super();
this._flow = _flow;
this._observers = new Set();
this._hasCompleted = false;
this._flow.addObserver(this);
}
addObserver(observer) {
this._observers.add(observer);
}
removeObserver(observer) {
if (!this._observers.has(observer)) {
throw new Errors_1.FlowRemoveObserverError();
}
this._observers.delete(observer);
}
emit(value) {
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
for (const observer of this._observers) {
observer.emit(value);
}
}
complete() {
if (this._hasCompleted) {
throw new Errors_1.HasCompletedError();
}
this._hasCompleted = true;
for (const observer of this._observers) {
observer.complete();
}
}
error(error) {
for (const observer of this._observers) {
observer?.error(error);
}
}
}
exports.SharedEventFlow = SharedEventFlow;
//# sourceMappingURL=Flow.js.map