Skip to content

Instantly share code, notes, and snippets.

@AlexRex
Last active August 20, 2021 17:31
Show Gist options
  • Save AlexRex/e0a7aaa9246ad35707d4967f28156939 to your computer and use it in GitHub Desktop.
Save AlexRex/e0a7aaa9246ad35707d4967f28156939 to your computer and use it in GitHub Desktop.
Result type helpers
import { ApplicationErrorCodes } from './application-error-codes'
export class ApplicationError extends Error {
constructor(
public readonly errorCode: string,
description?: string
) {
super(description || errorCode)
// Need to override the prototype
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, ApplicationError.prototype)
}
}
export const fromError = (error: Error) => new ApplicationError(ApplicationErrorCodes.INTERNAL_ERROR, error.message)
export type Ok<T> = {
value: T
}
export const ok = <T>(data: T): Ok<T> => ({
value: data
})
export type Result<T, E extends ApplicationError = ApplicationError> = Ok<T> | E
export type PromiseResult<T, E extends ApplicationError = ApplicationError> = Promise<Result<T, E>>
/**
* Expects a function that may throw. If it throws it returns the error passed
*/
export const fromAsyncTryCatch = <O extends Error = Error, E extends ApplicationError = ApplicationError>(errorFn: (originalError: O) => E) =>
async <DataType>(fn: () => Promise<DataType>): PromiseResult<DataType, E> => {
try {
const res = await fn()
return ok<DataType>(res)
} catch (catchErr) {
return errorFn(catchErr)
}
}
export const fromTryCatch = <O extends Error = Error, E extends ApplicationError = ApplicationError>(errorFn: (originalError: O) => E) =>
<DataType>(fn: () => DataType): Result<DataType, E> => {
try {
const res = fn()
return ok<DataType>(res)
} catch (catchErr) {
return errorFn(catchErr)
}
}
export const effectiveError = fromAsyncTryCatch<Error | ApplicationError, ApplicationError>(
(error) => {
if (error instanceof ApplicationError) {
return error
}
return fromError(error)
}
)
export const isErrorResult = <T = unknown, E extends ApplicationError = ApplicationError>(result: Result<T, E>): result is E =>
'errorCode' in result
export const isOkResult = <T = unknown, E extends ApplicationError = ApplicationError>(result: Result<T, E>): result is Ok<T> =>
'value' in result
export const filterOkResults = <T = unknown, E extends ApplicationError = ApplicationError>(results: Array<Result<T, E>>) =>
results.filter((result): result is Ok<T> => isOkResult(result))
export const filterErrorResults = <T = unknown, E extends ApplicationError = ApplicationError>(results: Array<Result<T, E>>) =>
results.filter((result): result is E => isErrorResult(result))
export const extractOkResults = <T = unknown>(oks: Array<Ok<T>>) => oks.map(({ value }) => value)
export const isOkResults = <T = unknown, E extends ApplicationError = ApplicationError>(results: Array<Result<T, E>>) =>
results.every((result): result is Ok<T> => isOkResult(result))
/**
* Example function
*/
const returningResult = (num: number): PromiseResult<number> =>
effectiveError(async () => {
if (num === 0) {
throw new Error('hack') // effectiveError will transform it into an application error
}
return 1
})
const usingFn = async () => {
const result = await returningResult(0)
if (isErrorResult(result)) {
return console.log(result) // Application error
}
return result.value + 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment