Vue / Editorial solution guide

Prevent stale asynchronous results from replacing current state

Identify the current operation and invalidate obsolete work before publishing its data or error.

About 3 min readContent reviewed

Read-only knowledge base · No public comments

Recognize the symptoms

A fast query briefly shows correct results and then reverts to an older query, a detail page displays the previous id, or a canceled operation shows an error after navigation. Rerunning the same steps may pass because completion order changed. A timing-dependent symptom needs controlled operation identity, not a longer timeout or an assumption that the last-started promise must finish last.

Understand the likely causes

  • Concurrent reads may resolve out of order; the loader may ignore abort or complete before it is observed; cleanup can be registered too late; or account and route scope may be absent from publication rules. A shared loading flag can also be cleared by the wrong operation. Cancellation and current-result validation are related but separate controls.

Record identity and publication order

Use synthetic ids to trace start, invalidation, completion, and state publication for each operation. Record the relevant query, route, account scope, and owner without logging private content or credentials. Reproduce with a fake loader whose completions can be reversed deliberately. Distinguish an obsolete response from an active operation’s real failure. Do not contact a production endpoint to create the race. The course’s pure latest-result example verifies one local publication policy, while the watcher integration still needs a compatible Vue test environment.

Register cleanup before awaiting work

The illustrative watcher uses a cleanup flag to reject obsolete publication. An abort-capable loader can also receive a signal to stop wasted work, but the guard remains necessary when cancellation races or is unsupported. Vue 3.5’s onWatcherCleanup must be registered synchronously; the callback-provided cleanup below also makes invalidation explicit. Watch the task source rather than an already-read primitive. The injected load function is an application contract and must classify its own data and transport behavior; no real request is executed by this guide.

javascript A contextual invalidation guard around an injected loader
import { ref, watch } from "vue"

export function useLatestValue(source, load) {
  const state = ref({ status: "idle", value: null })
  watch(source, async (input, _old, onCleanup) => {
    let current = true
    onCleanup(() => { current = false })
    state.value = { ...state.value, status: "loading" }
    try {
      const value = await load(input)
      if (current) state.value = { status: "ready", value }
    } catch {
      if (current) state.value = { ...state.value, status: "failed" }
    }
  })
  return { state }
}

Invoke synchronously under a managed Vue owner with a supported source. The snippet guards publication but does not cancel transport. It requires Vue and a loader implementation and was not runtime-tested; it sends no request during authoring.

Separate read freshness from write consistency

Make loading and error updates belong to the same identity as the data result, and include account or route changes in invalidation where required. A generation token can protect code paths that are not naturally owned by one watcher. For writes, aborting the client does not reverse a server action already accepted. Preserve ambiguous outcomes and use the backend’s documented idempotency and reconciliation contract. Retrying with a fresh operation identity can duplicate effects. Do not reuse a read-cancellation helper as a universal write-safety mechanism.

Verify the fix

Under a controlled fake, start two operations, complete the newer one first, then complete or fail the older one. Verify current data, loading, and error remain attached to the newer task. Repeat with unmount, route reuse, and account changes where relevant, and verify cleanup happens. Keep the old attempt’s diagnostic record. A passing pure coordinator assertion does not establish watcher disposal or browser behavior; report the exact integration tests actually performed.

Keep it from recurring

  • Define async ownership, result identity, cancellation, error mapping, and retry semantics when introducing a data feature. Use deterministic fake outcomes instead of sleeps. Review cleanup registration against the Vue version and avoid creating unmanaged watchers in later callbacks. Validate remote data before accepting it and keep private payloads out of race diagnostics.

Official references