Skip to content

Instantly share code, notes, and snippets.

@suin
Last active June 26, 2020 12:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suin/43071b43bfd4f3f6cf672dc4bbe56dbb to your computer and use it in GitHub Desktop.
Save suin/43071b43bfd4f3f6cf672dc4bbe56dbb to your computer and use it in GitHub Desktop.
import {Result} from './Result'
/**
* A value that represents either a asynchronous success or a asynchronous
* failure, including values to respective cases.
*
* @package
*/
export class AsyncResult<Value, _Error extends Error = Error> {
private constructor(readonly futureResult: Promise<Result<Value, _Error>>) {
}
/**
* Returns a new asynchronous result, running the given `execute` function.
*/
static of<Value, _Error extends Error = Error>(
execute: (
success: (value: Value) => void,
failure: (error: _Error) => void,
) => void,
): Result.Async<Value, _Error> {
return new AsyncResult(
new Promise(
resolve => execute(
value => resolve(Result.success(value)),
error => resolve(Result.failure(error)),
),
),
)
}
/**
* Return a new asynchronous success, sorting `undefined` as the `Value`.
*/
static success<_Error extends Error>(): Result.Async<undefined, _Error>
/**
* Returns a new asynchronous success, storing a `Value`.
*/
static success<Value, _Error extends Error>(
value: Value,
): Result.Async<Value, _Error>
static success<Value, _Error extends Error>(
value?: Value,
): Result.Async<Value | undefined, _Error> {
return new AsyncResult(Promise.resolve(Result.success(value)))
}
/**
* Returns a new asynchronous failure, storing a `Error` value.
* @param error
*/
static failure<Value, _Error extends Error>(
error: _Error,
): Result.Async<Value, _Error> {
return new AsyncResult(Promise.resolve(Result.failure(error)))
}
/**
* Returns `Promise<true>` if this result is a success.
*/
get isSuccess(): Promise<boolean> {
return this.onResolved(result => result.isSuccess)
}
/**
* Returns `Promise<true>` if this result is a failure.
*/
get isFailure(): Promise<boolean> {
return this.onResolved(result => result.isFailure)
}
/**
* Returns the `Promise<Value>` if this result is a success, otherwise the
* given `defaultValue` wrapped by `Promise`.
*/
async getOrDefault<DefaultValue>(
defaultValue: DefaultValue,
): Promise<Value | DefaultValue> {
return (await this.futureResult).getOrDefault(defaultValue)
}
/**
* Returns the `Promise<Value>` if this result is a success, otherwise
* `Promise<null>`.
*/
async getOrNull(): Promise<Value | null> {
return (await this.futureResult).getOrNull()
}
/**
* Returns the `Promise<Value>` if this result is a success, otherwise the
* new value that the given `recover` function returns, wrapping the value
* with `Promise`.
*/
async getOrElse<NewValue>(
recover: (error: _Error) => NewValue,
): Promise<Value | NewValue> {
return (await this.futureResult).getOrElse(recover)
}
/**
* Returns the `Promise<Value>` if this result is a success, otherwise throws
* the `Error` value.
*/
async getOrThrow(): Promise<Value> {
return (await this.futureResult).getOrThrow()
}
async toPromise(): Promise<Value> {
return this.getOrThrow()
}
get valueOrError(): Promise<Value | _Error> {
return this.onResolved(result => result.valueOrError)
}
/**
* Returns the `Promise<Error>` if this result is a failure, otherwise
* `Promise<null>`.
*/
async errorOrNull(): Promise<_Error | null> {
return (await this.futureResult).errorOrNull()
}
/**
* Returns the new Promise-wrapped value by applying the given `success`
* function to the `Value` if this result is a success. Or, returns the new
* Promise-wrapped value by applying the given `failure` function to the
* `Error` if this result is a failure.
*/
async fold<NewValue>(transform: {
readonly success: (value: Value) => NewValue
readonly failure: (error: _Error) => NewValue
}): Promise<NewValue> {
return (await this.futureResult).fold(transform)
}
/**
* Returns a new result by mapping the given `success` function to the
* `Value` if this result is a success. Or, returns a new result by mapping
* the given `failure` function to the `Error` value if this is a failure.
*/
when<NewValue, RecoveredValue>({success, failure}: {
readonly success: (value: Value) => NewValue
readonly failure: (error: _Error) => RecoveredValue
}): Result.Async<NewValue | RecoveredValue, never> {
return this.cloneWith(result => result.when({success, failure}))
}
/**
* Returns a new asynchronous result by mapping any success value using the
* given `transform`, leaving an `Error` value untouched.
*/
map<NewValue>(
transform: (value: Value) => NewValue,
): Result.Async<NewValue, _Error> {
return this.cloneWith(result => result.map(transform))
}
/**
* Returns a new result, storing the given `newValue` if this result is a
* success.
*/
mapTo<NewValue>(newValue: NewValue): Result.Async<NewValue, _Error> {
return this.cloneWith(result => result.mapTo(newValue))
}
/**
* Returns a new result, discarding the value if this result is a success.
*/
discardValue(): Result.Async<void, _Error> {
return this.cloneWith(result => result.discardValue())
}
/**
* Returns a new asynchronous result by mapping any success value using the
* given `transform`, leaving an `Error` value untouched.
*/
mapAsync<NewValue, NewError extends Error>(
transform: (value: Value) => Result.Async<NewValue, NewError>,
): Result.Async<NewValue, _Error | NewError> {
return this.cloneWith(result => result.mapAsync(transform).futureResult)
}
/**
* Returns a new asynchronous result by mapping any `Error` value using the
* given `transform`, leaving the `Value` untouched.
*/
mapError<NewError extends Error>(
transform: (error: _Error) => NewError,
): Result.Async<Value, NewError> {
return this.cloneWith(result => result.mapError(transform))
}
/**
* Returns a new asynchronous, applying `transform` to any success value.
*/
flatMap<NewValue, NewError extends Error>(
transform: (value: Value) => Result<NewValue, NewError>,
): Result.Async<NewValue, _Error | NewError> {
return this.cloneWith(result => result.flatMap(transform))
}
/**
* Returns a new asynchronous result by mapping any `Error` value using the
* given `recover`.
*/
recover<RecoveredValue>(
recover: (error: _Error) => RecoveredValue,
): Result.Async<Value | RecoveredValue, never> {
return this.cloneWith(result => result.recover(recover))
}
/**
* Returns a new asynchronous result by mapping any `Error` value using the
* given `recover`.
*/
recoverAsync<NewValue, NewError extends Error>(
recover: (error: _Error) => Result.Async<NewValue, NewError>,
): Result.Async<Value | NewValue, NewError> {
return this.cloneWith(result => result.recoverAsync(recover).futureResult)
}
/**
* Returns a new asynchronous result, applying `recover` to any `Error` value.
*/
flatRecover<NewValue, NewError extends Error>(
recover: (error: _Error) => Result<NewValue, NewError>,
): Result.Async<Value | NewValue, NewError> {
return this.cloneWith(result => result.flatRecover(recover))
}
/**
* Performs the given `success` function on any success value if this result
* is a success, or the given `failure` function on any `Error` value if this
* is a failure, returning the original asynchronous result unchanged.
*/
on(actions: {
readonly success: (value: Value) => void
readonly failure: (error: _Error) => void
}): this {
// noinspection JSIgnoredPromiseFromCall
this.onResolved(result => result.on(actions))
return this
}
/**
* Performs the given `action` on any success value if this result is a
* success, returning the original asynchronous result unchanged.
*/
onSuccess(action: (value: Value) => void): this {
// noinspection JSIgnoredPromiseFromCall
this.onResolved(result => result.onSuccess(action))
return this
}
/**
* Performs the given `action` on any `Error` if this result is a failure,
* returning the original asynchronous result unchanged.
*/
onFailure(action: (error: _Error) => void): this {
// noinspection JSIgnoredPromiseFromCall
this.onResolved(result => result.onFailure(action))
return this
}
static async equals(
asyncResult1: Result.Async<unknown>,
asyncResult2: Result.Async<unknown>,
): Promise<boolean> {
const [result1, result2] = await Promise.all([
asyncResult1.futureResult,
asyncResult2.futureResult,
])
return Result.equals(result1, result2)
}
static async equalsBy<Value1,
Error1 extends Error, Value2,
Error2 extends Error>(
asyncResult1: Result.Async<Value1, Error1>,
asyncResult2: Result.Async<Value2, Error2>,
by: {
readonly success: (value1: Value1, value2: Value2) => boolean,
readonly failure: (error1: Error1, error2: Error2) => boolean,
}): Promise<boolean> {
const [result1, result2] = await Promise.all([
asyncResult1.futureResult,
asyncResult2.futureResult,
])
return Result.equalsBy(result1, result2, by)
}
private onResolved<T>(
transform: (result: Result<Value, _Error>) => T | Promise<T>,
): Promise<T> {
return this.futureResult.then(transform)
}
private cloneWith<NewValue, NewError extends Error>(
transform: (result: Result<Value, _Error>) =>
| Result<NewValue, NewError>
| Promise<Result<NewValue, NewError>>,
): Result.Async<NewValue, NewError> {
return new AsyncResult(this.onResolved(transform))
}
}
import {Result} from './Result'
describe('Result', () => {
const error = new Error()
describe('success', () => {
it('returns a new success result', () => {
const value = {}
const result = Result.success(value)
expect(result.isSuccess).toBe(true)
expect(result.getOrNull()).toBe(value)
})
it('returns Result<undefined> if the value is omitted', () => {
const result = Result.success()
expect(result.getOrNull()).toBe(undefined)
})
it('returns Result<void> if the value is type `void`', () => {
const value: void = undefined
const result: Result<void> = Result.success(value)
expect(result.getOrNull()).toBe(undefined)
})
})
describe('failure', () => {
it('returns a new failure result', () => {
const result = Result.failure(error)
expect(result.isFailure).toBe(true)
expect(result.errorOrNull()).toBe(error)
})
})
describe('successAsync', () => {
it('returns a new asynchronous success result', async () => {
const value = {}
const result = Result.successAsync(value)
expect(await result.isSuccess).toBe(true)
expect(await result.getOrNull()).toBe(value)
})
it('returns AsyncResult<undefined> if the value is omitted', async () => {
const result = Result.successAsync()
expect(await result.getOrNull()).toBe(undefined)
})
it('returns AsyncResult<void> if the value is type `void`', async () => {
const value: void = undefined
const result: Result.Async<void> = Result.successAsync(value)
expect(await result.getOrNull()).toBe(undefined)
})
})
describe('failureAsync', () => {
it('returns a new asynchronous failure result', async () => {
const result = Result.failureAsync(error)
expect(await result.isFailure).toBe(true)
expect(await result.errorOrNull()).toBe(error)
})
})
describe('async', () => {
it('returns a new asynchronous success', async () => {
const result = Result.async(success => success(1))
expect(await result.getOrNull()).toBe(1)
})
it('returns a new asynchronous failure', async () => {
const result = Result.async((success, failure) => failure(error))
expect(await result.errorOrNull()).toBe(error)
})
})
describe('isSuccess', () => {
it('returns `true` if the result is a success', () => {
expect(Result.success().isSuccess).toBe(true)
})
it('returns `false` if the result is a failure', () => {
expect(Result.failure(error).isSuccess).toBe(false)
})
})
describe('isFailure', () => {
it('returns `true` if the result is a failure', () => {
expect(Result.failure(error).isFailure).toBe(true)
})
it('returns `false` if the result is a success', () => {
expect(Result.success().isFailure).toBe(false)
})
})
describe('getOrDefault', () => {
it('returns the `Value` if the result is a success', () => {
const result = Result.success(1)
expect(result.getOrDefault(0)).toBe(1)
})
it('returns the given default value if the result is failure', () => {
const result = Result.failure(error)
expect(result.getOrDefault(0)).toBe(0)
})
})
describe('getOrNull', () => {
it('returns the `Value` if the result is a success', () => {
const result = Result.success(1)
expect(result.getOrNull()).toBe(1)
})
it('returns `null` if the result is a failure', () => {
const result = Result.failure(error)
expect(result.getOrNull()).toBe(null)
})
})
describe('getOrElse', () => {
it('returns the `Value` if the result is a success', () => {
const result = Result.success(1)
expect(result.getOrElse(() => 0)).toBe(1)
})
it('returns a new value returned by the given `recover` function', () => {
const result = Result.failure(error)
expect(result.getOrElse(() => 0)).toBe(0)
})
test('the `recover` function receives the `Error` value', done => {
const result = Result.failure(error)
result.getOrElse(error1 => {
expect(error1).toBe(error)
done()
})
})
})
describe('getOrThrow', () => {
it('returns the `Value` if the result is a success', () => {
const success = Result.success(1)
expect(success.getOrThrow()).toBe(1)
})
it('throws the `Error` value if the result is a failure', () => {
const failure = Result.failure(error)
expect(() => failure.getOrThrow()).toThrow(error)
})
})
describe('errorOrNull', () => {
it('returns the `Error` if the result is a failure', () => {
const result = Result.failure(error)
expect(result.errorOrNull()).toBe(error)
})
it('returns `null` if the result is a success', () => {
const result = Result.success(1)
expect(result.errorOrNull()).toBe(null)
})
})
describe('fold', () => {
it(
'returns the new value the given success function to the `Value` if ' +
'the result is a success',
() => {
const result = Result.success(0)
const value = result.fold({success: () => 1, failure: () => -1})
expect(value).toBe(1)
},
)
it(
'returns the new value by applying the given failure function to the ' +
'`Error` if the result is a failure ',
() => {
const result = Result.failure(error)
const value = result.fold({success: () => 1, failure: () => -1})
expect(value).toBe(-1)
},
)
})
describe('map', () => {
it(
'returns a new result by mapping any success value using the given ' +
'`transform`',
() => {
const result1 = Result.success(1)
const result2 = result1.map(value => value + 1)
expect(result2.getOrNull()).toBe(2)
},
)
it(`doesn't touch the Error value`, () => {
const result1 = Result.failure<number>(error)
const result2 = result1.map(value => value + 1)
expect(result2.errorOrNull()).toBe(error)
})
})
describe('mapAsync', () => {
describe(
'returns a new asynchronous result by mapping any success value using ' +
'the given `transform`',
() => {
test('the transform returns a success', async () => {
const result1 = Result.success(1)
const result2 = result1.mapAsync(
value => Result.successAsync(value + 1),
)
expect(await result2.getOrNull()).toBe(2)
})
test('the transform returns a failure', async () => {
const result1 = Result.success(1)
const result2 = result1.mapAsync(() => Result.failureAsync(error))
expect(await result2.errorOrNull()).toBe(error)
})
},
)
it(`doesn't touch the Error value`, async () => {
const result1 = Result.failure<number>(error)
const result2 = result1.mapAsync(value => Result.successAsync(value + 1))
expect(await result2.errorOrNull()).toBe(error)
})
})
describe('mapError', () => {
it(
'returns a new result by mapping any `Error` value using the given ' +
'`transform`',
() => {
const newError = new Error()
const result1 = Result.failure(error)
const result2 = result1.mapError(error1 => {
expect(error1).toBe(error)
return newError
})
expect(result2.errorOrNull()).toBe(newError)
},
)
it(`doesn't touch the Value`, () => {
const result1 = Result.success(1)
const result2 = result1.mapError(() => new Error())
expect(result2.getOrNull()).toBe(1)
})
})
describe('flatMap', () => {
describe(
'returns the result returned by the given `transform`, applying ' +
'`transform` to any success value',
() => {
test('the transform returns a success', () => {
const result1 = Result.success(1)
const result2 = result1.flatMap(value => Result.success(value + 1))
expect(result2.getOrNull()).toBe(2)
})
test('the transform returns a failure', () => {
const result1 = Result.success(1)
const result2 = result1.flatMap(() => Result.failure(error))
expect(result2.errorOrNull()).toBe(error)
})
},
)
it('doesn\'t touch the `Error` value', () => {
const result1 = Result.failure<number>(error)
const result2 = result1.flatMap(value => Result.success(value + 1))
expect(result2.errorOrNull()).toBe(error)
})
})
describe('recover', () => {
it(
'returns a new result by mapping any `Error` value using the given ' +
'recover',
() => {
const result1 = Result.failure<number>(error)
const result2 = result1.recover(error1 => {
expect(error1).toBe(error)
return 1
})
expect(result2.getOrNull()).toBe(1)
},
)
it('doesn\'t touch the `Value`', () => {
const result1 = Result.success(1)
const result2 = result1.recover(() => 0)
expect(Result.equals(result1, result2)).toBe(true)
})
})
describe('recoverAsync', () => {
describe(
'returns a new asynchronous result by mapping any `Error` value using ' +
'the given recover',
() => {
test('the `recover` returns a success', async () => {
const result1 = Result.failure(error)
const result2 = result1.recoverAsync(error1 => {
expect(error1).toBe(error)
return Result.successAsync(1)
})
expect(await result2.getOrNull()).toBe(1)
})
test('the `recover` returns a failure', async () => {
const newError = new Error()
const result1 = Result.failure(error)
const result2 = result1.recoverAsync(error1 => {
expect(error1).toBe(error)
return Result.failureAsync(newError)
})
expect(await result2.errorOrNull()).toBe(newError)
})
},
)
it('doesn\'t touch the `Value`', async () => {
const result1 = Result.success(1)
const result2 = result1.recoverAsync(() => Result.successAsync(0))
expect(await result2.getOrNull()).toBe(1)
})
})
describe('flatRecover', () => {
describe(
'returns the result returned by the given `recover`, applying recover ' +
'to any Error value',
() => {
test('the `recover` returns a success', () => {
const result1 = Result.failure(error)
const result2 = result1.flatRecover(error1 => {
expect(error1).toBe(error)
return Result.success(1)
})
expect(result2.getOrNull()).toBe(1)
})
test('the `recover` returns a failure', () => {
const newError = new Error()
const result1 = Result.failure(error)
const result2 = result1.flatRecover(error1 => {
expect(error1).toBe(error)
return Result.failure(newError)
})
expect(result2.errorOrNull()).toBe(newError)
})
},
)
it('doesn\'t touch the `Value`', () => {
const result1 = Result.success(1)
const result2 = result1.flatRecover(() => Result.success(0))
expect(result2.getOrNull()).toBe(1)
})
})
describe('on', () => {
it(
'performs the given `success` function on any success value if this ' +
'result is a success',
() => {
let successCalled = false
let failureCalled = false
Result.success(1).on({
success: value => {
expect(value).toBe(1)
successCalled = true
},
failure: () => failureCalled = true,
})
expect(successCalled).toBe(true)
expect(failureCalled).toBe(false)
},
)
it(
'performs the given failure function on any Error value if this is a ' +
'failure',
() => {
let successCalled = false
let failureCalled = false
Result.failure(error).on({
success: () => successCalled = true,
failure: error1 => {
expect(error1).toBe(error)
return failureCalled = true
},
})
expect(successCalled).toBe(false)
expect(failureCalled).toBe(true)
},
)
it('returns the original result unchanged', () => {
const result1 = Result.success(1)
const result2 = result1.on({success: () => null, failure: () => null})
expect(result1).toBe(result2)
})
})
describe('onSuccess', () => {
it(
'performs the given `action` on any success value if the result is a ' +
'success',
() => {
const result = Result.success(1)
let called = false
result.onSuccess(value => {
expect(value).toBe(1)
called = true
})
expect(called).toBe(true)
},
)
it('returns the original result unchanged', () => {
const result1 = Result.success(1)
const result2 = result1.onSuccess(() => null)
expect(result2).toBe(result1)
})
it('doesn\'t call the given `action` if the result is a failure', () => {
const result = Result.failure(error)
let called = false
result.onSuccess(() => called = true)
expect(called).toBe(false)
})
})
describe('onFailure', () => {
it(
'performs the given `action` on any `Error` if the result is a failure',
() => {
const result = Result.failure(error)
let called = false
result.onFailure(error1 => {
expect(error1).toBe(error)
called = true
})
expect(called).toBe(true)
},
)
it('returns the original result unchanged', () => {
const result1 = Result.failure(error)
const result2 = result1.onFailure(() => null)
expect(result2).toBe(result1)
})
it('doesn\'t call the given `action` if the result is a success', () => {
const result = Result.success(1)
let called = false
result.onFailure(() => called = true)
expect(called).toBe(false)
})
})
})
import {AsyncResult} from './AsyncResult'
export namespace Result {
export type Async<Value, _Error extends Error = Error> = AsyncResult<Value, _Error>
}
/**
* A value that represents either a success or a failure, including values to
* respective cases.
*/
export class Result<Value, _Error extends Error = Error> {
private constructor(private readonly result: Success<Value> | _Error) {
Object.defineProperty(this, 'unchanged', {enumerable: false})
}
/**
* Return a new success, sorting `undefined` as the `Value`.
*/
static success<_Error extends Error>(): Result<undefined, _Error>
/**
* Returns a new success, storing a `Value`.
*/
static success<Value, _Error extends Error = Error>(
value: Value,
): Result<Value, _Error>
static success<Value, _Error extends Error = Error>(
value?: Value,
): Result<Value | undefined, _Error> {
return new Result<Value | undefined, _Error>(new Success(value))
}
/**
* Returns a new failure, storing a `Error`.
*/
static failure<Value, _Error extends Error = Error>(
error: _Error,
): Result<Value, _Error> {
return new Result(error)
}
/**
* Return a new asynchronous success, sorting `undefined` as the `Value`.
*/
static successAsync<_Error extends Error>(): AsyncResult<undefined, _Error>
/**
* Returns a new asynchronous success, storing a `Value`.
*/
static successAsync<Value, _Error extends Error = Error>(
value: Value,
): AsyncResult<Value, _Error>
static successAsync<Value, _Error extends Error>(
value?: Value,
): AsyncResult<Value | undefined, _Error> {
return AsyncResult.success(value)
}
/**
* Returns a new asynchronous failure, storing a `Error`.
*/
static failureAsync<Value, _Error extends Error = Error>(
error: _Error,
): AsyncResult<Value, _Error> {
return AsyncResult.failure(error)
}
/**
* Returns a new asynchronous result, running the given `execute` function.
*/
static async<Value, _Error extends Error = Error>(
execute: (
success: (value: Value) => void,
failure: (error: _Error) => void,
) => void,
): AsyncResult<Value, _Error> {
return AsyncResult.of(execute)
}
/**
* Returns `true` if the given `value` is a result.
*/
static isResult<Value = unknown>(value: unknown): value is Result<Value> {
return value instanceof Result
}
/**
* Returns `true` if the given `value` is an asynchronous result.
*/
static isAsyncResult<Value = unknown>(value: unknown): value is AsyncResult<Value> {
return value instanceof AsyncResult
}
/**
* Returns `true` if this result is a success.
*/
get isSuccess(): boolean {
return this.result instanceof Success
}
/**
* Returns `true` if this result is a failure.
*/
get isFailure(): boolean {
return !this.isSuccess
}
/**
* Returns the `Value` if this result is a success, otherwise the given
* `defaultValue`.
*/
getOrDefault<DefaultValue>(defaultValue: DefaultValue): Value | DefaultValue {
return this.getOrElse(_ => defaultValue)
}
/**
* Returns the `Value` if this result is a success, otherwise `null`.
*/
getOrNull(): Value | null {
return this.getOrDefault(null)
}
/**
* Returns the `Value` if this result is a success, otherwise the new value
* that the given `recover` function returns.
*/
getOrElse<NewValue>(recover: (error: _Error) => NewValue): Value | NewValue {
return this.case({success: value => value, failure: recover})
}
/**
* Returns the `Value` if this result is a success, otherwise throws the
* `Error`.
* @throws {Error}
*/
getOrThrow(): Value {
return this.case({
success: value => value,
failure: error => {
throw error
},
})
}
get valueOrError(): Value | _Error {
return this.case({
success: value => value,
failure: error => error,
})
}
/**
* Returns the `Error` if this result is a failure, otherwise `null`.
*/
errorOrNull(): _Error | null {
return this.case({success: _ => null, failure: error => error})
}
/**
* Returns the new value by applying the given `success` function to the
* `Value` if this result is a success. Or, returns the new value by applying
* the given `failure` function to the `Error` if this result is a failure.
*/
fold<NewValue>({success, failure}: {
readonly success: (value: Value) => NewValue
readonly failure: (error: _Error) => NewValue
}): NewValue {
return this.case({success, failure})
}
/**
* Returns a new result by mapping the given `success` function to the
* `Value` if this result is a success. Or, returns a new result by mapping
* the given `failure` function to the `Error` value if this is a failure.
*/
when<NewValue, RecoveredValue>({success, failure}: {
readonly success: (value: Value) => NewValue
readonly failure: (error: _Error) => RecoveredValue
}): Result<NewValue | RecoveredValue, never> {
return this.map(success).recover(failure)
}
/**
* Returns a new result by mapping any success value using the given
* `transform`, leaving an `Error` value untouched.
*/
map<NewValue>(
transform: (value: Value) => NewValue,
): Result<NewValue, _Error> {
return this.flatMap(value => Result.success(transform(value)))
}
/**
* Returns a new result, storing the given `newValue` if this result is a
* success.
*/
mapTo<NewValue>(newValue: NewValue): Result<NewValue, _Error> {
return this.map(() => newValue)
}
/**
* Returns a new result, discarding the value if this result is a success.
*/
discardValue(): Result<void, _Error> {
return this.mapTo(undefined)
}
/**
* Returns a new asynchronous result by mapping any success value using the
* given `transform`, leaving an `Error` value untouched.
*/
mapAsync<NewValue, NewError extends Error>(
transform: (value: Value) => AsyncResult<NewValue, NewError>,
): AsyncResult<NewValue, _Error | NewError> {
return this.fold<AsyncResult<NewValue, _Error | NewError>>({
success: transform,
failure: Result.failureAsync,
})
}
/**
* Returns a new result by mapping any `Error` value using the given
* `transform`, leaving the `Value` untouched.
*/
mapError<NewError extends Error>(
transform: (error: _Error) => NewError,
): Result<Value, NewError> {
return this.fold({
success: this.unchanged,
failure: error => Result.failure(transform(error)),
})
}
/**
* Returns the result returned by the given `transform`, applying `transform`
* to any success value.
*/
flatMap<NewValue, NewError extends Error>(
transform: (value: Value) => Result<NewValue, NewError>,
): Result<NewValue, _Error | NewError> {
return this.fold({success: transform, failure: this.unchanged})
}
/**
* Returns a new result by mapping any `Error` value using the given
* `recover`.
*/
recover<RecoveredValue>(
recover: (error: _Error) => RecoveredValue,
): Result<Value | RecoveredValue, never> {
return this.flatRecover(error => Result.success(recover(error)))
}
/**
* Returns a new asynchronous result by mapping any `Error` value using the
* given `recover`.
*/
recoverAsync<RecoveredValue, NewError extends Error>(
recover: (error: _Error) => AsyncResult<RecoveredValue, NewError>,
): AsyncResult<Value | RecoveredValue, NewError> {
return this.fold<AsyncResult<Value | RecoveredValue, NewError>>({
success: Result.successAsync,
failure: recover,
})
}
/**
* Returns the result returned by the given `recover` , applying `recover` to
* any `Error` value.
*/
flatRecover<RecoveredValue, NewError extends Error>(
recover: (error: _Error) => Result<RecoveredValue, NewError>,
): Result<Value | RecoveredValue, NewError> {
return this.fold({success: this.unchanged, failure: recover})
}
/**
* Performs the given `success` function on any success value if this result
* is a success, or the given `failure` function on any `Error` value if this
* is a failure, returning the original result unchanged.
*/
on({success, failure}: {
readonly success: (value: Value) => void
readonly failure: (error: _Error) => void
}): this {
this.fold({success, failure})
return this
}
/**
* Performs the given `action` on any success value if this result is a
* success, returning the original result unchanged.
*/
onSuccess(action: (value: Value) => void): this {
return this.on({success: action, failure: _ => null})
}
/**
* Performs the given `action` on any `Error` if this result is a failure,
* returning the original result unchanged.
*/
onFailure(action: (error: _Error) => void): this {
return this.on({success: _ => null, failure: action})
}
static equals(result1: Result<unknown>, result2: Result<unknown>): boolean {
return this.equalsBy(result1, result2, {
success: (value1, value2) => value1 === value2,
failure: (error1, error2) => error1 === error2,
})
}
static equalsBy<Value1, Error1 extends Error, Value2, Error2 extends Error>(
result1: Result<Value1, Error1>,
result2: Result<Value2, Error2>,
{success: valueEquals, failure: errorEquals}: {
readonly success: (value1: Value1, value2: Value2) => boolean,
readonly failure: (error1: Error1, error2: Error2) => boolean,
},
): boolean {
return result1.fold({
success: value1 => result2.fold({
success: value2 => valueEquals(value1, value2),
failure: _ => false,
}),
failure: error1 => result2.fold({
failure: error2 => errorEquals(error1, error2),
success: _ => false,
}),
})
}
static async equalsAsync(
asyncResult1: AsyncResult<unknown>,
asyncResult2: AsyncResult<unknown>,
): Promise<boolean> {
return AsyncResult.equals(asyncResult1, asyncResult2)
}
static async equalsAsyncBy<Value1,
Error1 extends Error,
Value2,
Error2 extends Error>(
asyncResult1: AsyncResult<Value1, Error1>,
asyncResult2: AsyncResult<Value2, Error2>,
by: {
readonly success: (value1: Value1, value2: Value2) => boolean,
readonly failure: (error1: Error1, error2: Error2) => boolean,
},
): Promise<boolean> {
return AsyncResult.equalsBy(asyncResult1, asyncResult2, by)
}
private case<NewValue, RecoveredValue>({success, failure}: {
readonly success: (value: Value) => NewValue
readonly failure: (error: _Error) => RecoveredValue
}): NewValue | RecoveredValue {
return this.result instanceof Success
? success(this.result.value)
: failure(this.result)
}
/**
* Returns this result unchanged.
*/
private unchanged = (): Result<any, any> => this
}
/**
* A value represents a successful state, including any success value.
*/
class Success<Value> {
constructor(readonly value: Value) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment