Multi-Loader
The first AsyncLoader argument loader accepts an array, and the multiLoaderStrategy option decides how to orchestrate multiple loader functions: failover (fallback) or multi-source merge (combine). Multi-loader scenarios reuse the full set of capabilities: caching / abort / timeout / retry / fallback / multiplex.
fallback: failover
Try loaders in order; the first success stops the rest; on a loader failure, try the next; all failures count as one failed attempt.
import { AsyncLoader } from "asyncsignal/loader";
// Fall back to backup endpoints when the primary fails
const loader = new AsyncLoader(
[
async (args) => (await fetch("/api/primary", { signal: args.abortSignal })).json(),
async (args) => (await fetch("/api/secondary", { signal: args.abortSignal })).json(),
async (args) => (await fetch("/api/cdn", { signal: args.abortSignal })).json(),
],
{ multiLoaderStrategy: "fallback", retry: 1 }
);
const data = await loader.get(); // try in order until one succeedsfallback does not call onResults
fallback only does "failover on error" and never calls onResults (ignored even if configured). onResults is a merge hook specific to combine.
combine: multi-source merge
All loaders run concurrently; results (successful T, failed Error) are collected and passed to onResults to merge. Uses lenient semantics (Promise.allSettled): a single loader failure does not abort the others — it just enters results as an Error.
// Fetch user info and orders concurrently and merge into one object
const loader = new AsyncLoader(
[
async (args) => (await fetch("/api/user", { signal: args.abortSignal })).json(),
async (args) => (await fetch("/api/orders", { signal: args.abortSignal })).json(),
],
{
multiLoaderStrategy: "combine",
onResults: (results, defaultValue) => {
// Merge successful items; failed items are Error instances
const [user, orders] = results;
if (user instanceof Error || orders instanceof Error) {
return defaultValue as { user: any; orders: any }; // fall back on partial failure
}
return { user, orders };
},
}
);
const { user, orders } = await loader.get();onResults in depth
onResults(results, defaultValue) => T is the result merge hook for the combine strategy:
| Argument | Description |
|---|---|
results | Array of all loaders' results (in loaders order); successful items are T, failed items are Error objects |
defaultValue | The value from options.defaultValue (may be undefined), available for the hook to fall back |
- Returns the merged result
T, which becomes the return value ofget()and is written to the cache. - A synchronous throw is treated as a failed attempt (goes through the
retry/onRejected/defaultValueterminal flow). - This is a transformation function with a return value (not an event callback) — errors must propagate, and it does not go through the error-swallowing mechanism of
onFulfilled/onRejected.
combine requires onResults
When multiLoaderStrategy="combine" and there is more than one loader, you must provide onResults; otherwise construction throws: multiLoaderStrategy="combine" requires onResults.
Interaction with other capabilities
| Capability | Interaction with multi-loader |
|---|---|
timeout | per-attempt: multiple loaders share the timeout window of the current attempt (both fallback/combine) |
retry | On fallback all-failed / combine final failure (including onResults throws), the entire loaders sequence is retried according to retry |
abort() | Aborts the inflight loader(s); onRejected is fired synchronously by abort() |
defaultValue | Both passed to onResults as an argument (for its decision) and used as the final fallback resolve on failure |
multiplex | On reuse, the first instance's loaders array wins; loader(s) passed by the new instance are ignored |
hash | Auto-generated from the loaders array (order-sensitive: [a,b] differs from [b,a]) |
Single-loader backward compatibility
When a single loader (or a one-element array) is passed, multiLoaderStrategy has no effect and behavior is identical to before the change.