@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
JavaScript
/**
* @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