UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

138 lines 4.66 kB
/** * @module Async */ import { callInvokable, isInvokable, resolveOneOrMore, TimeSpan, } from "../../../../utilities/_module-exports.js"; import { HedgingAsyncError } from "../../../../async/async.errors.js"; import { timeoutAndFail, abortAndFail } from "../../../../async/utilities/_module.js"; /** * @internal */ class ResolvedError extends Error { constructor(message, cause) { super(message, { cause }); this.name = ResolvedError.name; } } /** * The `concurrentHedging` middleware executes the primary function and all fallback functions concurrently. * It returns the result of the first successful function and automatically aborts all remaining functions. * If all function fail than error is thrown. * * IMPORT_PATH: `"@daiso-tech/core/async"` * @group Middlewares * @throws {HedgingAsyncError} {@link HedgingAsyncError} * * @example * ```ts * import { concurrentHedging } from "@daiso-tech/core/async"; * import { AsyncHooks } from "@daiso-tech/core/utilities"; * * async function fn1(signal?: AbortSignal): Promise<unknown> { * const response = await fetch("ENDPOINT-1", { signal }); * return await response.json(); * } * async function fn2(signal?: AbortSignal): Promise<unknown> { * const response = await fetch("ENDPOINT-2", { signal }); * return await response.json(); * } * async function fn3(signal?: AbortSignal): Promise<unknown> { * const response = await fetch("ENDPOINT-3", { signal }); * return await response.json(); * } * const fetchData = new AsyncHooks(fn1, [ * concurrentHedging({ * fallbacks: [ * fn2, * fn3 * ] * }) * ], { * signalBinder: { * getSignal: (args) => args[0], * forwardSignal: (args, signal) => { * args[0] = signal; * } * } * }); * * console.log(await fetchData.invoke()); * ``` */ export function concurrentHedging(settings) { const { waitTime = TimeSpan.fromSeconds(2), fallbacks, onHedgeAttempt = () => { }, onHedgeError = () => { }, } = settings; const resolvedFallbacks = resolveOneOrMore(fallbacks).map((fallback, index) => { if (isInvokable(fallback)) { return { name: `fallback-${String(index + 1)}`, func: fallback, }; } return fallback; }); return async (args, next, { context, abort, signal }) => { function step1({ name, func }) { return { name, promise: timeoutAndFail(abortAndFail((async () => callInvokable(func, ...args))(), signal), waitTime, (error) => { abort(error); }, signal), }; } async function step2({ name, promise }) { try { const value = await promise; // We abort all other promises when on promise succeds. abort(new ResolvedError("Already resolved")); return { type: "success", value, name, }; } catch (error) { return { type: "failure", error, name, }; } } const funcs = [ { name: "__initial", func: next, }, ...resolvedFallbacks, ]; const promises = funcs.map(step1).map(step2); const errors = []; const promiseResults = await Promise.all(promises); for (const promiseResult of promiseResults) { callInvokable(onHedgeAttempt, { args, context, name: promiseResult.name, }); // We return the first fulfilled value if (promiseResult.type === "success") { return promiseResult.value; } else if (!(promiseResult.error instanceof ResolvedError)) { callInvokable(onHedgeError, { args, context, error: promiseResult.error, name: promiseResult.name, }); errors.push(promiseResult.error); } } // If all promiseResults are rejected we will throw an error const funcNames = funcs .slice(1) .map(({ name }) => `"${name}"`) .join(", "); throw new HedgingAsyncError(`The original function and fallback functions failed: ${funcNames}`, errors); }; } //# sourceMappingURL=concurrent-hedging.middleware.js.map