UNPKG

typescript-functional-extensions

Version:

A TypeScript implementation of synchronous and asynchronous Maybe and Result monads

345 lines (344 loc) 13.4 kB
"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); }; }