Last active
November 6, 2020 17:50
-
-
Save akatakritos/42ecdd56463d056afb18e628eb924b03 to your computer and use it in GitHub Desktop.
rxjs error handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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()); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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