@o3r/core
Version:
Core of the Otter Framework
75 lines • 3.91 kB
JavaScript
import { BehaviorSubject, EMPTY, from, identity, isObservable, merge, of, } from 'rxjs';
import { catchError, delay, filter, finalize, pairwise, startWith, switchMap, tap, } from 'rxjs/operators';
import { isIdentifiedCallAction, } from './async.helpers';
/**
* Determine if the given parameter is a Promise
* @param object
*/
const isPromise = (object) => object && typeof object === 'object' && typeof object.then !== 'undefined';
/**
* Custom operator to use instead of SwitchMap with effects based on FromApi actions.
* It makes sure to emit an action when the inner subscription is unsubscribed in order to keep the store up-to-date with pending information.
* @param successHandler function that returns the action to emit in case the FromApi call is a success
* @param errorHandler function that returns the action to emit in case the FromApi call fails
* @param cancelRequestActionFactory function that returns the action to emit in case the FromApi action is 'cancelled' because a new action was received by the switchMap
*/
export function fromApiEffectSwitchMap(successHandler, errorHandler, cancelRequestActionFactory) {
const pendingRequestIdsContext = {};
return (source$) => source$.pipe(tap((action) => {
if (isIdentifiedCallAction(action)) {
pendingRequestIdsContext[action.requestId] = true;
}
}), startWith(undefined), pairwise(), switchMap(([previousAction, action]) => {
if (!action) {
return EMPTY;
}
const isPreviousActionStillRunning = isIdentifiedCallAction(previousAction) && pendingRequestIdsContext[previousAction.requestId];
const cleanStack = () => {
if (isIdentifiedCallAction(action)) {
delete pendingRequestIdsContext[action.requestId];
}
};
return from(action.call).pipe(tap(cleanStack), switchMap((result) => {
const success = successHandler(result, action);
return isObservable(success) ? success : (isPromise(success) ? success : of(success));
}), catchError((error) => {
cleanStack();
return errorHandler?.(error, action) || EMPTY;
}), isPreviousActionStillRunning && cancelRequestActionFactory ? startWith(cancelRequestActionFactory({ requestId: previousAction.requestId }, action)) : identity);
}));
}
/**
* Same as {@link fromApiEffectSwitchMap}, instead one inner subscription is kept by id.
* @param successHandler
* @param errorHandler
* @param cancelRequestActionFactory
* @param cleanUpTimer
*/
export function fromApiEffectSwitchMapById(successHandler, errorHandler, cancelRequestActionFactory, cleanUpTimer) {
const innerSourcesById = {};
return (source$) => {
return source$.pipe(filter((action) => {
if (!isIdentifiedCallAction(action) || !action.id) {
return false;
}
if (isIdentifiedCallAction(action) && innerSourcesById[action.id]) {
innerSourcesById[action.id][0].next(action);
return false;
}
return true;
}), switchMap((action) => {
const newIdSubject = new BehaviorSubject(action);
const newId$ = newIdSubject.pipe(fromApiEffectSwitchMap(successHandler, errorHandler, cancelRequestActionFactory));
innerSourcesById[action.id] = [newIdSubject, newId$];
if (cleanUpTimer !== undefined) {
newIdSubject.pipe(switchMap((myAction) => from(myAction.call).pipe(delay(cleanUpTimer), finalize(() => {
delete innerSourcesById[myAction.id];
newIdSubject.complete();
})))).subscribe();
}
const streams = Object.values(innerSourcesById).map(([_, obs]) => obs);
return merge(...streams);
}));
};
}
//# sourceMappingURL=async.operators.js.map