FetchMixin
Auto-generated @jbrowse/mobx-state-tree API for the current JBrowse release — see pluggable elements for concepts. Built into JBrowse core. View source.
Cancel-safe fetch lifecycle for any display that loads data over RPC. Owns
the entire fetch state machine (abort rotation, staleness tracking,
error capture, status reporting); consumers see only runFetch,
cancelFetch, isLoading, error, statusMessage, and fetchGeneration.
Volatiles
| Member | Description |
|---|---|
activeSignalactiveSignal: undefined as AbortSignal | undefined | signal of the in-flight fetch, or undefined when idle |
fetchGenerationfetchGeneration: 0 | bumps at every fetch end; autoruns read it to re-evaluate, and it doubles as the staleness epoch inside runFetch |
reloadCounterreloadCounter: 0 | Bumped by reload() and read unconditionally by the fetch autoruns, so a user retry re-runs the body even where nothing else moved — after an error every other fetch input is unchanged. It is also the half that survives a reload() override that forgets to invalidate, which is the dead Retry button makeRetryContractCheck reports. Declared here because this is the one mixin every fetch foundation composes, the same argument that put fetchInert below; the comparative family carried its own until ADR-105. |
statusWindowstatusWindow: createStatusWindow(writeStatus(self)) | This display's status field, and the only thing that writes it: one throttle window, one slot per concurrent operation, so N parallel per-region fetches thin to one stream between them rather than N and a second operation cannot end the first one's label (ADR-081). Lent whole to createAbortRotation by a display that also runs a bare-autorun fetch — see StatusReporter. |
errorerror: undefined as unknown | last non-abort fetch error, or undefined |
statusMessagestatusMessage: undefined as string | undefined | work-in-progress status string |
statusProgressstatusProgress: undefined as number | undefined | determinate progress fraction [0,1] for the current status, or undefined when the in-flight phase is indeterminate |
fetchCanceledfetchCanceled: false | true after the user explicitly cancels a load (the loading overlay's cancel button → cancelFetchByUser). A durable, blocking state — unlike cancelFetch, it does not retrigger the fetch autoruns — so the load stays stopped until the user retries (reload) or the viewport changes. Any new fetch clears it (runFetch resets it at the start). |
| fetchRotation | The latest-wins machine this mixin is a wrapper around, and not a second one: createAbortRotation owns abort rotation, the isCurrent guard, the status slot and the supersede-versus-end rule (ADR-080, ADR-081), for every fetch in the codebase that has one. runFetch adds the observable bookkeeping a display needs on top — isLoading, error, fetchGeneration, fetchCanceled — and nothing else.It was two implementations of that machine until 2026-08-20, which is how they came to disagree about whether a completed fetch releases its signal. A display's primary fetch is this wrapper; a second concurrent fetch on the same node holds a rotation of its own, which is why the primitive is the thing that exists and this is the thing built on it (ADR-054 §1, the one section ADR-105 keeps). It is lent this display's statusWindow, so the fetch takes a slot on the one field rather than opening a second window over it — the whole point of StatusReporter. |
Getters
| Member | Description |
|---|---|
isLoadingboolean | true while a fetch is active |
isLoadingOrCanceledboolean | isLoading widened to cover a user-canceled load: what a hover gate wants, since neither state has a frame on screen that a hit describes. Not a phase input — computeActivityPhase reads the two apart, because a cancel is finished (canceled) where a fetch is not (loading). |
fetchInertboolean | Overridable hook (default false): the states where this display deliberately never fetches, so it holds no data and none is coming. Sequence sets it past base resolution ("Zoom in to see sequence"); LD sets it with the triangle toggled off. One hook has three readers. A display that gains such a state declares it once, which covers the reader it would otherwise forget, always the one outside the display: - the phase ( computeActivityPhase), which otherwise parks a scrim over the placeholder, or a canceled overlay once Cancel is clicked; - the SVG export (computeSvgReady's extraTerminal), whose awaitSvgReady is an unbounded when, so one such display hangs the whole view's export; - the retry contract check (makeRetryContractCheck), which would otherwise report a dead Retry on a display correctly declining to load anything.fetchInert replaces three hooks: loadingSuppressed, svgReadyExtraTerminal on each of the two foundations, and fetchInert on the comparative family, which had already collapsed them. Both LGV displays that override it returned one expression for all three, and the global family hard-coded one of the three to false for a while, so LD could express only half its state. All three fetch families declare it here since the comparative one composed this mixin (ADR-105), so the retry check reads one field everywhere. ADR-082.A hook rather than a displayPhase override, because overriding the getter means restating the whole loading condition. Sequence held a verbatim copy of the other terms that way, and a copy misses any term added to the condition later.fetchInert lives here because this is the one mixin all three display foundations compose. Same argument, one level down, that put rendersCanvas on RenderLifecycleMixin beside canvasDrawn. |
awaitingPrerequisiteboolean | Overridable hook (default false), read only by the retry contract check (makeRetryContractCheck): "this run declined because a prerequisite fetch in another autorun has not landed, and its arrival wakes this one again". It defers the retry verdict to that later run rather than waiving it, so a display cannot spend its retry on a decline it called preliminary.Two displays set it, one per fetch foundation, so it lives beside fetchInert rather than on either: HiC's contacts fetch declines until CoreGetInfo lands, and MultiSampleVariantBaseModel's fetchNeeded declines until sourcesBase does. Both have a reload() that wakes the prerequisite's autorun as well as their own.It has to be strictly narrower than the gate it explains. One that restates the gate's negation makes every decline a deferred one. No run is then ever judged, which exempts the display from the check. HiC does this deliberately, because its gate and its prerequisite are one condition; what covers its retry instead is LinearHicDisplay/infoFetchFailure.test.ts.Not for a display deliberately not fetching at all — that is fetchInert above, which the loading scrim and the export read too. |
awaitingDependentDataboolean | Overridable hook (default false), read by computeActivityPhase: a load this display depends on beyond its primary fetch has not landed for the first time, so the frame the primary fetch calls current is still missing something. Multi-way synteny sets it until its lane genes and lane links first arrive, so an export or a capture never lands between the ortholog fetch and the gene models that fill the lanes.A hook rather than a displayPhase override, for the reason fetchInert is one: that display carried the override, restating the foundation's two arguments verbatim to append one term, and a copy like that misses any term added to the foundation later.Not dataSuperseded, which holds the export through every later refetch too: a display saying this wants the scrim on the first landing only, since later lane fetches redraw over lanes already on screen. |
settingsFetchInputsunknown | The settings axis every fetch family invalidates on: this display's rpcProps() payload and its adapter config, as one value compared structurally. The per-region family watches it from SettingsInvalidate and stamps it on each region, the keyed families fold it into currentFetchKey, and the byte gate measures under it — one getter, so no two can come to invalidate on different axes.undefined inside the payload is a real state and a class instance compares by its own fields, which a serialized key could not say; makeSettingsFetchInputs has why. |
Actions
| Member | Description |
|---|---|
setError(error?: unknown) => void | |
setStatusMessage(status?: RpcStatus | undefined) => void | Unthrottled: a display writing a phase label by hand must see every write land. The high-frequency RPC stream is thinned one level up, by the streams self.statusWindow hands out — and aggregated across them, which a write by hand is not. |
stopActiveFetch() => void | Abort the in-flight fetch (if any) and retire its slot. The shared preamble of both cancel paths; the difference between them is only what they do to fetchCanceled / fetchGeneration afterward. |
openStatusStream(isCurrent: () => boolean) => StatusStream | Open one operation's slot on the display's status field: an RPC statusCallback throttled through the display-wide window and guarded so a callback that fires after the node is torn down (RPCs resolve their status stream asynchronously) is a safe no-op, plus the clear that retires the slot when the operation ends.Every operation on the display opens one, and the two come back together because an operation that never retires keeps reporting status for a phase that is over. The viewport fetch ( runFetch), the clustering run and a lent createAbortRotation are three of them on one field; before ADR-081 each blanked the field outright and the last one to finish overwrote the status of the other two.isCurrent is required and has no "node is alive" default, because a live node is not enough: a superseded fetch is on a live node, and its late status repainting the overlay of the fetch that replaced it is the failure this guards. runFetch passes !isStale(), and every display gets that through ctx.statusCallback unasked; a caller outside a fetch (the clustering autorun) passes its own run's flag. Defaulting to isAlive made the loose answer the easy one and five displays took it.runFetch's own slot is opened by the rotation; this is for an operation outside any fetch, the tree sidebar's clustering run. |
cancelFetch() => void | cancel any in-flight fetch and bump fetchGeneration (always bumps, so callers can retrigger fetch autoruns even when nothing was in flight). This is the internal reset clearAllRpcData runs — it clears any user-cancel flag so the retrigger actually re-fetches. |
cancelFetchByUser() => void | User-initiated cancel from the loading overlay. Stops the in-flight fetch and lands in a durable fetchCanceled state. Unlike cancelFetch, it does NOT bump fetchGeneration — so the fetch autoruns don't immediately restart the load. The user retries via reload (the overlay's retry button), or it clears on the next viewport change. |
reload() => void | The display's half of the retry contract (DISPLAYCHROME.md), in the one mixin every fetching LGV display composes: clear the error the banner is showing, clear the durable user-cancel synchronously so the overlay flips from "canceled" to "loading" now rather than when the debounced fetch begins, and bump the counter every fetch trigger reads unconditionally. Each foundation chains this and adds the one invalidation its own freshness gate needs; a display with extra teardown chains the foundation's. reloadReachesCounter.test.ts reads every reload() in the tree for the override that forgets. |
beforeDestroy() => void | Abort an in-flight fetch on teardown. Without this, a display destroyed mid-fetch (track/view closed while loading) never signals the worker to abort the now-useless work, and its in-flight HTTP reads keep downloading. MST auto-chains lifecycle hooks, so a composing display can still define its own beforeDestroy. |
beginFetch(signal: AbortSignal) => void | The onBegin half of a fetch's bookkeeping: publish the in-flight signal (isLoading) and clear the durable user-cancel — a load starting is the single clear point that covers every retrigger path (reload, viewport change, settings invalidate). An action of its own for the same reason endFetch is: installFetch's lifecycle callbacks run outside any MST flow this mixin owns. |
endFetch(current: boolean) => void | The finally half of runFetch's bookkeeping, an action of its own because runFetchOnce's finally resumes on a microtask the flow does not own — a direct volatile write there is outside the action context, which is the one thing hoisting the sequence into a shared function costs. The stale branch is a superseded fetch, which must not clear the loading flag the run that replaced it just set. |
runFetch(work: (ctx: FetchContext) => Promise<void>) => Promise<void> | Run a cancel-safe fetch (cancels any prior). The work callback gets a FetchContext with a signal to forward to the RPC and an isStale() check to short-circuit commits once the user has moved on. The MST-flow wrapper over the shared runFetchOnce sequence, and only the wrapper: the begin/clear/run/commit/error/end order, and the rules that keep a superseded run from writing back, are the same function every other fetch in the tree runs. What this adds is the observable bookkeeping a display needs — isLoading through activeSignal, fetchGeneration, the user-cancel clear — and the flow itself, which is an action, so work's synchronous prefix runs untracked wherever a fetch autorun calls this. |
Related links
- Guide: Data fetching pipeline