Skip to content

Instantly share code, notes, and snippets.

@akatakritos
Last active November 6, 2020 17:50
Show Gist options
  • Save akatakritos/42ecdd56463d056afb18e628eb924b03 to your computer and use it in GitHub Desktop.
Save akatakritos/42ecdd56463d056afb18e628eb924b03 to your computer and use it in GitHub Desktop.
rxjs error handling
/**
* rxjs operator to convert a observable to an obserable of Results
*/
export function wrapResult<T>(): UnaryFunction<Observable<T>, Observable<Result<T, Error>>> {
return pipe(
map<T, Ok<T, Error>>((emitted) => new Ok<T, Error>(emitted)),
catchError((error: Error) => of(new Err<T, Error>(error)))
);
}
export function mapResult<T, U>(fn: (val: T) => U) {
return pipe(map((result: Result<T, Error>) => result.map(fn)));
}
/**
* Filters an observable of Result<T, E> to only those that are Ok
*/
export function filterOk<T, E>() {
return pipe(
filter((result: Result<T, E>) => result.isOk()),
map((result: Ok<T, E>) => result.value)
);
}
/**
* Unwraps a result and runs a switchMap operation with the underlying value
*
* If the input as Err, the output will be Err, otherwise the switchMap is run and wrapped in Result
* @param f
*/
export function switchMapResult<T, TResult>(f: (val: T) => Observable<TResult>) {
return switchMap((result: Result<T, Error>) => {
if (result.isErr()) {
return of(new Err<TResult, Error>(result.error));
}
return f(result.value).pipe(wrapResult());
});
}
interface ResultClass<T, E> {
/**
* Tells you if this result is successful, and if so, you can retrieve the `value` property
*/
isOk(): this is Ok<T, E>;
/**
* Tells you if the result was an error, and if so, you can retrieve the `error` property
*/
isErr(): this is Err<T, E>;
/**
* Maps a successful value of one type to a successful value of another. If it was an error, the error is propogated
* @param fn - mapping function to invoke over the successful value
*/
map<U>(fn: (val: T) => U): Result<U, E>;
valueOr(defaultValue: T): T;
}
/**
* A Result is either `Ok` with the value you want, or `Err` with some kind
* of error data.
*/
export type Result<T, E = Error> = Ok<T, E> | Err<T, E>;
/**
* Represents the successful case
*/
export class Ok<T, E = Error> implements ResultClass<T, E> {
constructor(public readonly value: T) {}
isOk(): this is Ok<T, E> {
return true;
}
isErr(): this is Err<T, E> {
return false;
}
map<U>(fn: (a: T) => U): Result<U, E> {
return new Ok<U, E>(fn(this.value));
}
valueOr(defaultValue: T) {
return this.value;
}
}
export class Err<T, E = Error> implements ResultClass<T, E> {
constructor(public readonly error: E) {}
isOk(): this is Ok<T, E> {
return false;
}
isErr(): this is Err<T, E> {
return true;
}
map<U>(fn: (a: T) => U): Result<U, E> {
return (this as unknown) as Err<U, E>;
}
valueOr(defaultValue: T) {
return defaultValue;
}
}
import { Component, Input, Directive, ContentChild, TemplateRef } from '@angular/core';
import { Result } from './util/result';
/** @private */
@Directive({
selector: '[isOk]',
})
export class IsOkDirective {
constructor(public template: TemplateRef<unknown>) {}
}
/** @private */
@Directive({
selector: '[isErr]',
})
export class IsErrDirective {
constructor(public template: TemplateRef<unknown>) {}
}
/**
* Assists with rendering Result<T, E> types in a template. Provide two children to the component, one marked with [isOk] and one with [isErr].
* The correct branch will be rendered, and the value or error object will be provided to it as context:
*
* ```html
* <unwrap-result [result]"someResultValue">
* <ng-template isOk let-value>
* {{ value | json }}
* </ng-template>
* <ng-template isErr let-error>
* {{ error | json }}
* </ng-template>
* </unwrap-result>
* ```
*/
@Component({
selector: 'unwrap-result',
template: `
<ng-container *ngIf="isOkTemplate && result?.isOk()">
<ng-container *ngTemplateOutlet="isOkTemplate.template; context: context"></ng-container>
</ng-container>
<ng-container *ngIf="isErrTemplate && result?.isErr()">
<ng-container *ngTemplateOutlet="isErrTemplate.template; context: context"></ng-container>
</ng-container>
`,
})
export class UnwrapResultComponent<T, E> {
@Input() result: Result<T, E>;
@ContentChild(IsOkDirective) isOkTemplate: TemplateRef<IsOkDirective>;
@ContentChild(IsErrDirective) isErrTemplate: TemplateRef<IsErrDirective>;
get context() {
return { $implicit: this.result.isOk() ? this.result.value : this.result.error };
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment