Last active
June 26, 2020 12:36
-
-
Save suin/43071b43bfd4f3f6cf672dc4bbe56dbb to your computer and use it in GitHub Desktop.
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 {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)) | |
} | |
} |
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 {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) | |
}) | |
}) | |
}) |
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 {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