typescript-functional-extensions
Version:
A TypeScript implementation of synchronous and asynchronous Maybe and Result monads
345 lines (344 loc) • 13.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResultAsync = void 0;
const result_js_1 = require("./result.js");
const unit_js_1 = require("./unit.js");
const utilities_js_1 = require("./utilities.js");
/**
* Represents and asynchronous Result that could succeed with a value or fail with an error
*/
class ResultAsync {
/**
* Combines several results (and any error messages) into a single result.
* The returned result will be a failure if any of the input results are failures.
*
* @param results The Results to be combined.
* @returns A Result that is a success when all the input results are also successes.
*/
static combine(results) {
const promises = Object.values(results).map((result) => result instanceof result_js_1.Result ? Promise.resolve(result) : result.toPromise());
const promiseResult = Promise.all(promises).then((resolvedResults) => {
const valuesAndErrors = Object.keys(results).reduce((sink, key, index) => {
const resolvedResult = resolvedResults[index];
resolvedResult.isSuccess
? (sink.values[key] = resolvedResult.getValueOrThrow())
: sink.errors.push(resolvedResult.getErrorOrThrow());
return sink;
}, {
errors: [],
values: {},
});
return valuesAndErrors.errors.length
? result_js_1.Result.failure(valuesAndErrors.errors.join(', '))
: result_js_1.Result.success(valuesAndErrors.values);
});
return ResultAsync.from(promiseResult);
}
/**
* Combines several results (and any error messages) into a single result.
* The returned result will be a failure if any of the input results are failures.
*
* Asynchronous operations are executed one after another.
*
* @param results The Results to be combined.
* @returns A Result that is a success when all the input results are also successes.
*/
static combineInOrder(results) {
const entries = Object.entries(results);
const promiseResult = entries
.reduce(async (accumulator, [key, result]) => {
const sink = await accumulator;
const resolvedResult = result instanceof result_js_1.Result ? result : await result.toPromise();
resolvedResult.isSuccess
? (sink.values[key] = resolvedResult.getValueOrThrow())
: sink.errors.push(resolvedResult.getErrorOrThrow());
return sink;
}, Promise.resolve({
errors: [],
values: {},
}))
.then((valuesAndErrors) => valuesAndErrors.errors.length
? result_js_1.Result.failure(valuesAndErrors.errors.join(', '))
: result_js_1.Result.success(valuesAndErrors.values));
return ResultAsync.from(promiseResult);
}
static from(value) {
if ((0, utilities_js_1.isPromise)(value)) {
return new ResultAsync(value.then((v) => v instanceof result_js_1.Result ? v : result_js_1.Result.success(v)));
}
else if (value instanceof result_js_1.Result) {
return new ResultAsync(Promise.resolve(value));
}
throw new Error('Value must be a Promise or Result instance');
}
static try(promiseOrFunction, errorHandler) {
if ((0, utilities_js_1.isPromise)(promiseOrFunction)) {
return new ResultAsync(promiseOrFunction
.then((value) => result_js_1.Result.success(value))
.catch(unwrapHandledError(errorHandler)));
}
try {
return new ResultAsync(promiseOrFunction()
.then((value) => result_js_1.Result.success(value))
.catch(unwrapHandledError(errorHandler)));
}
catch (error) {
return new ResultAsync(unwrapHandledError(errorHandler)(error));
}
}
static success(value) {
const result = (0, utilities_js_1.isDefined)(value)
? result_js_1.Result.success(value)
: result_js_1.Result.success(unit_js_1.Unit.Instance);
return new ResultAsync(Promise.resolve(result));
}
/**
* Creates a new failed ResultAsync with the given error
* @param error
*/
static failure(error) {
return new ResultAsync(Promise.resolve(result_js_1.Result.failure(error)));
}
value;
constructor(value) {
this.value = value;
}
/**
* True if the Result was successful
*/
get isSuccess() {
return this.value.then((r) => r.isSuccess);
}
/**
* True if the Result failed
*/
get isFailure() {
return this.value.then((r) => r.isFailure);
}
/**
* Will return the inner value created from executing the Result
* if it was successful, otherwise it will throw an Error
* @returns the result of a successful Result
*/
getValueOrThrow() {
return this.value.then((r) => r.getValueOrThrow());
}
getValueOrDefault(defaultOrValueFactory) {
return this.value.then((r) => (0, utilities_js_1.isFunction)(defaultOrValueFactory)
? r.getValueOrDefault(defaultOrValueFactory)
: r.getValueOrDefault(defaultOrValueFactory));
}
/**
* Will return the inner error value of the Result
* if it failed, otherwise it will throw an Error
* @returns the error of a failed Result
*/
getErrorOrThrow() {
return this.value.then((r) => r.getErrorOrThrow());
}
getErrorOrDefault(defaultOrErrorCreator) {
return this.value.then((r) => {
return (0, utilities_js_1.isFunction)(defaultOrErrorCreator)
? r.getErrorOrDefault(defaultOrErrorCreator)
: r.getErrorOrDefault(defaultOrErrorCreator);
});
}
/**
* Checks the value of a given predicate against the Result's inner value,
* if the Result already succeeded
* @param predicate check against the Result's inner value
* @param errorOrErrorCreator either an error value or a function to create an error from the Result's inner value
* @returns a successful ResultAsync if the predicate is true, and a failed one if not
*/
ensure(predicate, errorOrErrorCreator) {
return new ResultAsync(this.value.then(async (result) => {
if (result.isFailure) {
return result;
}
const value = result.getValueOrThrow();
if (predicate(value)) {
return result;
}
if (!(0, utilities_js_1.isFunction)(errorOrErrorCreator)) {
return result_js_1.Result.failure(errorOrErrorCreator);
}
return unwrapHandledError(errorOrErrorCreator)(value);
}));
}
/**
* Executes the given operator functions, creating a custom pipeline
* @param operations ResultAsync operation functions
* @returns
*/
pipe(...operations) {
return (0, utilities_js_1.pipeFromArray)(operations)(this);
}
map(projection) {
return new ResultAsync(this.value.then((r) => {
if (r.isFailure) {
return result_js_1.Result.failure(r.getErrorOrThrow());
}
const promiseOrValue = projection(r.getValueOrThrow());
if ((0, utilities_js_1.isPromise)(promiseOrValue)) {
return promiseOrValue.then((v) => result_js_1.Result.success(v));
}
return result_js_1.Result.success(promiseOrValue);
}));
}
mapError(projection) {
return new ResultAsync(this.value.then((r) => {
if (r.isSuccess) {
return result_js_1.Result.success(r.getValueOrThrow());
}
const promiseOrError = projection(r.getErrorOrThrow());
if ((0, utilities_js_1.isPromise)(promiseOrError)) {
return promiseOrError.then((e) => result_js_1.Result.failure(e));
}
return result_js_1.Result.failure(promiseOrError);
}));
}
mapFailure(projection) {
return new ResultAsync(this.value.then((r) => {
if (r.isSuccess) {
return r;
}
const valueOrPromise = projection(r.getErrorOrThrow());
if ((0, utilities_js_1.isPromise)(valueOrPromise)) {
return valueOrPromise.then((v) => result_js_1.Result.success(v));
}
return result_js_1.Result.success(valueOrPromise);
}));
}
bind(projection) {
return new ResultAsync(this.value.then((r) => {
if (r.isFailure) {
return result_js_1.Result.failure(r.getErrorOrThrow());
}
const resultOrResultAsync = projection(r.getValueOrThrow());
if (resultOrResultAsync instanceof result_js_1.Result) {
return resultOrResultAsync;
}
return resultOrResultAsync.toPromise();
}));
}
/**
* Maps a failed ResultAsync to a new ResultAsync
* @deprecated Please use `compensate` instead
* @param projection
* @returns
*/
bindFailure(projection) {
return this.compensate(projection);
}
/**
* Maps a failed ResultAsync to a new ResultAsync
* @param projection
* @returns
*/
compensate(projection) {
return new ResultAsync(this.value.then((r) => {
if (r.isSuccess) {
return result_js_1.Result.success(r.getValueOrThrow());
}
const resultOrResultAsync = projection(r.getErrorOrThrow());
return resultOrResultAsync instanceof result_js_1.Result
? resultOrResultAsync
: resultOrResultAsync.toPromise();
}));
}
tap(action) {
return new ResultAsync(this.value.then(async (originalResult) => {
if (originalResult.isFailure) {
return originalResult;
}
const voidOrPromise = action(originalResult.getValueOrThrow());
if ((0, utilities_js_1.isPromise)(voidOrPromise)) {
await voidOrPromise;
}
return originalResult;
}));
}
tapIf(conditionOrPredicate, action) {
return new ResultAsync(this.value.then((r) => {
return (0, utilities_js_1.isFunction)(conditionOrPredicate)
? r.tapIf(conditionOrPredicate, action)
: r.tapIf(conditionOrPredicate, action);
}));
}
tapEither(action) {
return new ResultAsync(this.value.then(async (originalResult) => {
const actionResult = action();
if (actionResult instanceof Promise) {
await actionResult;
}
return originalResult;
}));
}
match(matcher) {
return this.value.then((r) => r.match(matcher));
}
/**
* Maps both failed and successful ResultAsync to a new value
* @param projection a function given the inner Result of the ResultAsync that returns a new value
* @returns
*/
finally(projection) {
return this.value.then((r) => r.finally(projection));
}
tapFailure(action) {
return new ResultAsync(this.value.then(async (r) => {
if (r.isSuccess) {
return r;
}
const error = r.getErrorOrThrow();
const result = action(error);
if ((0, utilities_js_1.isPromise)(result)) {
await result;
}
return result_js_1.Result.failure(error);
}));
}
onSuccessTry(action, errorHandler) {
return new ResultAsync(this.value.then(async (r) => {
if (r.isFailure) {
return r;
}
const value = r.getValueOrThrow();
try {
const result = action(value);
if ((0, utilities_js_1.isPromise)(result)) {
await result;
}
return result_js_1.Result.success(value);
}
catch (error) {
return result_js_1.Result.failure(errorHandler(error));
}
}));
}
/**
* Returns the inner Promise, wrapping a failed Result for a rejected Promise with the
* given errorHandler if provided, othewise rejected Promise handling
* is left to the caller.
* @param errorHandler a value or Promise returning error handler that converts a rejected Promise
* to a failed Result.
* @returns
*/
toPromise(errorHandler) {
if (!(0, utilities_js_1.isDefined)(errorHandler)) {
return this.value;
}
return this.value.catch(unwrapHandledError(errorHandler));
}
}
exports.ResultAsync = ResultAsync;
function unwrapHandledError(errorHandler) {
return async (error) => {
const handledResultOrPromise = errorHandler(error);
if (handledResultOrPromise instanceof Promise) {
const unwrappedResult = await handledResultOrPromise;
return result_js_1.Result.failure(unwrappedResult);
}
return result_js_1.Result.failure(handledResultOrPromise);
};
}