Created
May 15, 2019 13:05
-
-
Save punmechanic/797d334e0728842dcad064925e28748b to your computer and use it in GitHub Desktop.
awful effect-based HTTP. one benefit this provides is that it is more exhaustive than using a functional approach because it uses different 'Response' types. But it is quite verbose and would need more work to be a serious solution.
This file contains hidden or 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
// TODO: This does not work in mock mode. | |
import { HTTPAPI, NotFoundError } from "../api" | |
import React from "react" | |
type Maybe<T> = T | undefined | |
export interface Request<T> { | |
then<T2>(f: (data: Maybe<T>) => Maybe<T2>): Request<Maybe<T2>> | |
run(): Promise<Response<Maybe<T>>> | |
} | |
class PendingResponse<T> implements Response<Maybe<T>> { | |
map<T2>(): Response<T2> { | |
return new PendingResponse<T2>() | |
} | |
orElse<T2>(f: () => T2): T2 { | |
return f() | |
} | |
} | |
export interface Response<T> { | |
map<T2>(f: (data: Maybe<T>) => T2): Response<T2> | |
orElse<T2>(f: () => T2): T | T2 | |
} | |
class OkResponse<T> implements Response<T> { | |
readonly data: T | |
constructor(data: T) { | |
this.data = data | |
} | |
map<T2>(f: (data: Maybe<T>) => T2): Response<T2> { | |
return new OkResponse(f(this.data)) | |
} | |
orElse(): T { | |
return this.data | |
} | |
} | |
export function isPending<T>(response: Response<T>): boolean { | |
return response instanceof PendingResponse | |
} | |
export function pending<T>(): Response<Maybe<T>> { | |
return new PendingResponse<T>() | |
} | |
class TransformedRequest<TOriginal, TNext> implements Request<Maybe<TNext>> { | |
readonly original: Request<TOriginal> | |
private readonly f: (data: Maybe<TOriginal>) => Maybe<TNext> | |
constructor( | |
original: Request<TOriginal>, | |
f: (data: Maybe<TOriginal>) => Maybe<TNext> | |
) { | |
this.original = original | |
this.f = f | |
} | |
then<TNext2>( | |
f: (data: Maybe<TNext>) => Maybe<TNext2> | |
): Request<Maybe<TNext2>> { | |
return new TransformedRequest(this, f) | |
} | |
run(): Promise<Response<Maybe<TNext>>> { | |
return this.original.run().then(r => r.map(this.f)) | |
} | |
} | |
type HttpMethod = "get" | "delete" | |
export function useEffectualHttp<T>( | |
req: Request<T>, | |
watch: unknown[] | |
): Response<Maybe<T>> { | |
const [response, setResponse] = React.useState(pending<T>()) | |
React.useEffect(() => { | |
req | |
.run() | |
.then(setResponse) | |
.catch(setResponse) | |
}, [...watch]) | |
return response | |
} | |
class EffectualRequest<T> implements Request<T> { | |
readonly method: HttpMethod | |
readonly url: string | |
constructor(method: HttpMethod, url: string) { | |
this.method = method | |
this.url = url | |
} | |
then<T2>(f: (data: Maybe<T>) => Maybe<T2>): Request<Maybe<T2>> { | |
return new TransformedRequest<Maybe<T>, Maybe<T2>>(this, f) | |
} | |
run(): Promise<Response<Maybe<T>>> { | |
const api = new HTTPAPI() | |
const f = api[this.method] as (url: string) => Promise<T> | |
// Make sure to preserve 'this' | |
return f | |
.call(api, this.url) | |
.then(data => new OkResponse(data)) | |
.catch(e => { | |
if (e instanceof NotFoundError) { | |
return new OkResponse<Maybe<T>>(undefined) | |
} | |
return Promise.reject(e) | |
}) | |
} | |
} | |
export default class EffectualAPI { | |
get<T = unknown>(url: string): Request<T> { | |
return new EffectualRequest<T>("get", url) | |
} | |
delete<T = void>(url: string): Request<T> { | |
return new EffectualRequest<T>("delete", url) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment