Skip to content

refetchQueries with throwOnError leaves paused fetch rejection unhandled on client.clear() #12441

Closed

Description

@CaliLuke

Describe the bug

With @tanstack/query-core@5.104.5, refetchQueries with throwOnError: true resolves while a query is paused offline. Clearing the client afterward emits an unhandled CancelledError, even though the caller awaited the returned promise.

Resolving the public refetch promise while offline can be intentional; the unexpected behavior is the later unhandled rejection from the detached fetch promise.

Minimal reproduction

Install @tanstack/query-core@5.104.5, save this as repro.mjs, and run node repro.mjs:

import { QueryClient, onlineManager } from '@tanstack/query-core';

const errors = [];
process.on('unhandledRejection', (error) => errors.push(error));
const client = new QueryClient();
const queryKey = ['example'];
await client.fetchQuery({ queryKey, queryFn: async () => 'value' });
onlineManager.setOnline(false);
await client.refetchQueries({ queryKey }, { throwOnError: true });
console.log('fetchStatus:', client.getQueryState(queryKey).fetchStatus);
client.clear();
await new Promise((resolve) => setTimeout(resolve, 0));
console.log('unhandled rejections:', errors.map((error) => error.message));
onlineManager.setOnline(true);

Observed on Node.js v26.12.1:

fetchStatus: paused
unhandled rejections: [ 'CancelledError' ]

Expected behavior

Clearing/cancelling a paused refetch should not emit an unhandled rejection when its public promise was awaited. If paused refetches are intentionally detached from the returned aggregate, their later rejection still needs an observer.

Impact and possible cause

Offline cleanup or client replacement can produce an unhandled rejection in otherwise fully awaited code. In QueryClient.refetchQueries, query.fetch(...) is started with throwOnError: true, but the paused branch returns Promise.resolve() instead of retaining or handling that fetch promise. The cancellation is therefore no longer observed by the aggregate.

A consumer can retain the individual fetch promises, but that requires duplicating refetch target selection. Handling the detached promise in the existing paused branch would keep that behavior within the library.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions