From 0a2f5d12e65b3980fb3e39e705955cbc3b6cb726 Mon Sep 17 00:00:00 2001 From: Igor Lukanin Date: Mon, 22 Dec 2025 11:54:25 +0100 Subject: [PATCH] fix(client-core): return null instead of undefined when query is cancelled by mutex When multiple queries share the same mutexKey, newer queries invalidate older ones. Previously, cancelled queries would resolve to undefined because mutexPromise silently swallowed the MUTEX_ERROR without returning a value. This caused issues in useCubeQuery where resultSet would unexpectedly reset to undefined while isLoading remained false. Changes: - Add MutexCancelledError class for explicit mutex cancellation handling - Update mutexPromise to throw MutexCancelledError instead of swallowing errors - Catch MutexCancelledError in loadMethod and return null (a valid return type) Fixes #10261 --- packages/cubejs-client-core/src/index.ts | 31 +++++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/cubejs-client-core/src/index.ts b/packages/cubejs-client-core/src/index.ts index d20bdeb9bb91b..51326a44a9e76 100644 --- a/packages/cubejs-client-core/src/index.ts +++ b/packages/cubejs-client-core/src/index.ts @@ -150,14 +150,20 @@ let mutexCounter = 0; const MUTEX_ERROR = 'Mutex has been changed'; -function mutexPromise(promise: Promise) { - return promise - .then((result) => result) - .catch((error) => { - if (error !== MUTEX_ERROR) { - throw error; - } - }); +class MutexCancelledError extends Error { + constructor() { + super(MUTEX_ERROR); + this.name = 'MutexCancelledError'; + } +} + +function mutexPromise(promise: Promise): Promise { + return promise.catch((error) => { + if (error === MUTEX_ERROR) { + throw new MutexCancelledError(); + } + throw error; + }); } export type ResponseFormat = 'compact' | 'default' | undefined; @@ -412,7 +418,14 @@ class CubeApi { return subscribeNext(); }; - const promise = requestPromise.then(requestInstance => mutexPromise(requestInstance.subscribe(loadImpl))); + const promise = requestPromise + .then(requestInstance => mutexPromise(requestInstance.subscribe(loadImpl))) + .catch((error) => { + if (error instanceof MutexCancelledError) { + return null; + } + throw error; + }); if (callback) { return {