neverthrow-result-utils
Version:
Extra utilities for working with Neverthrow results
146 lines • 6.56 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResultUtils = void 0;
const delay_1 = __importDefault(require("delay"));
const neverthrow_1 = require("neverthrow");
class ResultUtils {
static combine(asyncResultList) {
return neverthrow_1.ResultAsync.combine(asyncResultList);
}
/**
* Given a list of ResultAsync, it will return the result only from the
* first that completes.
* @param asyncResultList
* @returns
*/
static race(asyncResultList) {
return neverthrow_1.ResultAsync.fromPromise(Promise.race(asyncResultList), (e) => {
return e;
}).andThen((result) => {
return result.isErr() ? (0, neverthrow_1.err)(result.error) : (0, neverthrow_1.ok)(result.value);
});
}
static executeSerially(funcList) {
const executor = async () => {
const results = new Array();
for (const func of funcList) {
const result = await func();
if (result.isErr()) {
throw result.error;
}
else {
results.push(result.value);
}
}
return results;
};
return neverthrow_1.ResultAsync.fromPromise(executor(), (e) => {
return e;
});
}
/**
* backoffAndRetry() is a powerful method that executes a function that returns
* a ResultAsync. If that result is OK, then the result is returned directly.
* If it is an error, it will check to see if the returned error is in the
* acceptableErrors list. If so, it will delay and re-execute the function.
* If it's not an acceptable error type, the error result is returned.
* Each failure will backoff exponentially.
*
* This is an excellent wrapper for functions that can fail inconsistently
* in some ways were we expect a retry to succeed. For example, a function
* that communicates with a database over a sketchy connection might just need
* to retry in a few seconds.
* @param func
* @param acceptableErrors A list of error types that are considered retryable. Logical error types that will never succeed should not be in this list, but error types that represent temporary runtime errors should.
* @param maxAttempts This is the maximum number of times a retry will be attempted; set to null to use infinite retries.
* @param baseSeconds This is the delay in seconds for the first retry attempt. It will double with each successive retry.
* @returns
*/
static backoffAndRetry(func, acceptableErrors, maxAttempts, baseSeconds = 5) {
if (maxAttempts != null && maxAttempts < 1) {
throw new Error("maxAttempts must be 1 or more!");
}
if (baseSeconds < 1) {
throw new Error("baseSeconds must be 1 or more!");
}
const runAndCheck = (currentAttempt, nextAttemptSecs, lastError) => {
if (maxAttempts != null && currentAttempt > maxAttempts) {
if (lastError == null) {
throw new Error("Error before first function run; logical error! maxAttempts must be 1 or more!");
}
return (0, neverthrow_1.errAsync)(lastError);
}
// Check the result. If it's not an error, we're done!
// If it's an error, check the error type against acceptableErrors. If it's in the list,
// wait some amount of time and try again.
// If it is not in the list, return the error and stop.
return func().orElse((e) => {
let retry = false;
for (const acceptableError of acceptableErrors) {
if (e instanceof acceptableError) {
retry = true;
break;
}
}
if (retry) {
return neverthrow_1.ResultAsync.fromSafePromise((0, delay_1.default)(nextAttemptSecs)).andThen(() => {
return runAndCheck(++currentAttempt, nextAttemptSecs * 2, e);
});
}
return (0, neverthrow_1.errAsync)(e);
});
};
return runAndCheck(1, baseSeconds, null);
}
/**
* filter() is a normal filter method that works with async callbacks.
* This works like a nomral array filter() call, except the callback returns
* a ResultAsync<boolean> instead of just boolean.
* @param arr the source array
* @param callback a function that returns a ResultAsync<boolean>; if it returns true then the source value is included in the result.
* @returns a ResultAsync containing an array of source values where the callback returns true.
*/
static filter(arr, callback) {
const filterVals = new Array();
return ResultUtils.combine(arr.map((val) => {
return callback(val).map((result) => {
if (result) {
filterVals.push(val);
}
});
})).map(() => {
return filterVals;
});
}
/**
* map() is a way to combine multiple async callbacks.
* This works like a normal array map() call, except the callbacks return ResultAsync.
* It will combine all the results and return a single ResultAsync with an array of the mapped
* values.
* @param arr The source array of objects
* @param callback A function that returns a ResultAsync and a value
* @returns A ResultAsync containing an array of the mapped values or the first error.
*/
static map(arr, callback) {
const mapVals = new Array();
return ResultUtils.combine(arr.map((val) => {
return callback(val).map((result) => {
mapVals.push(result);
});
})).map(() => {
return mapVals;
});
}
static delay(ms) {
return neverthrow_1.ResultAsync.fromSafePromise(new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
}));
}
}
exports.ResultUtils = ResultUtils;
//# sourceMappingURL=index.js.map