Skip to content

Instantly share code, notes, and snippets.

@alanthai
Created October 15, 2017 19:39
Show Gist options
  • Save alanthai/4daac365a5465bb109168c53f103ed2c to your computer and use it in GitHub Desktop.
Save alanthai/4daac365a5465bb109168c53f103ed2c to your computer and use it in GitHub Desktop.
enum WebStatus {
Loading,
Error,
Success,
NotCalled,
}
interface SuccessState<S> {
status: WebStatus.Success;
payload: S;
}
interface ErrorState<E> {
status: WebStatus.Error;
error: E;
}
interface LoadingState {
status: WebStatus.Loading;
}
interface NotCalledState {
status: WebStatus.NotCalled;
}
export type WebState<S, E = any>
= SuccessState<S>
| ErrorState<E>
| LoadingState
| NotCalledState;
export function successState<S>(payload: S): SuccessState<S> {
return { status: WebStatus.Success, payload };
}
export function errorState<E>(error: E): ErrorState<E> {
return { status: WebStatus.Error, error };
}
export function loadingState(): LoadingState {
return { status: WebStatus.Loading };
}
export function notCalledState(): NotCalledState {
return { status: WebStatus.NotCalled };
}
type MatchFns<S, E, T> = {
readonly success: (payload?: S) => T;
readonly error: (err?: E) => T;
readonly loading: () => T;
readonly notCalled: () => T;
readonly default?: (arg: S | E) => T;
} | {
readonly success?: (payload?: S) => T;
readonly error?: (err?: E) => T;
readonly loading?: () => T;
readonly notCalled?: () => T;
readonly default: (arg: S | E) => T;
};
export class WebCall<S, E> {
static create<SS, EE>(state: WebState<SS, EE>): WebCall<SS, EE>{
return new WebCall<SS, EE>(state);
}
constructor(private state: WebState<S, E>) {}
map<T>(fn: (arg: S) => T): WebCall<T, E> {
if (this.state.status === WebStatus.Success) {
return new WebCall<T, E>({
status: WebStatus.Success,
payload: fn(this.state.payload),
});
}
return new WebCall<T, E>(this.state);
}
match<T>(matchFns: MatchFns<S, E, T>) {
type Match = MatchFns<S, E, T>;
function throwMatchError(type: keyof Match) {
return () => {
throw new Error(`Cannot match. A ${type} or default is required.`);
};
}
function getMatchFunction<K extends keyof Match>(type: K): Match[K] {
return matchFns[type] || matchFns.default || throwMatchError(type);
}
switch (this.state.status) {
case WebStatus.Success:
return getMatchFunction('success')(this.state.payload);
case WebStatus.Error:
return getMatchFunction('error')(this.state.error);
case WebStatus.Loading:
return getMatchFunction('loading')();
case WebStatus.NotCalled:
return getMatchFunction('notCalled')();
}
}
}
// example
interface User {
name: string;
id: string;
}
const createUser = (id, name): User => ({ id, name });
const users = WebCall.create<User[], any>({
status: WebStatus.Success,
payload: [createUser('1', 'John'), createUser('2', 'Jane')]
});
const userNames = users.map<string[]>(list => list.map(user => user.name));
const names = userIds.match({
success: (names) => names.join(', '),
default: () => ''
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment