cube-js/cube · error · Error

CancelToken was already canceled

Error message

CancelToken was already canceled

What it means

CancelToken.cancel() guards against double-cancellation: if the token's `canceled` flag is already true, calling cancel() again throws 'CancelToken was already canceled'. The token coordinates cancellation of deferred/queued CancelablePromises; canceling is a one-shot transition. Callers that issue cancel() more than once (e.g. from multiple event paths) hit this error.

Solutions

  1. Check the token state before canceling, or track locally whether cancel was already issued on your side.
  2. Wrap cancel() in try/catch and treat the 'already canceled' error as a no-op success.
  3. Ensure a single owner/path is responsible for canceling a given token.
  4. Serialize cancellation logic (idempotent wrapper storing a promise of the first cancel).

Example fix

// before
await token.cancel();
await token.cancel(); // throws
// after
try { await token.cancel(); } catch (e) { /* already canceled: ignore */ }
Defensive patterns

Strategy: try-catch

Validate before calling

// track cancellation state on your side before calling
if (!alreadyCanceled) {
  await token.cancel();
  alreadyCanceled = true;
}

Type guard

const isAlreadyCanceled = (e: unknown): boolean =>
  e instanceof Error && e.message === 'CancelToken was already canceled';

Try / catch

try {
  await token.cancel();
} catch (e) {
  if (!isAlreadyCanceled(e)) throw e; // double-cancel is treated as success
}

Prevention

When it happens

Trigger: Calling token.cancel() twice — e.g. once from a request-abort handler and again on shutdown/timeout cleanup; or racing cancel() invocations that are not synchronized (the flag check is not atomic against concurrent async callers).

Common situations: Component unmount triggers cancel while a global timeout also cancels the same token; query orchestrator restart cancels tokens already canceled; duplicate event listeners each calling cancel().

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/d03358a86f678bbc. Report an issue: GitHub.

Appendix: source

Thrown at packages/cubejs-backend-shared/src/promises.ts:61

      resolve();
    };
  });
  promise.cancel = cancel;

  return promise;
}

class CancelToken {
  protected readonly deferred: (() => Promise<void> | void)[] = [];

  protected readonly withQueue: CancelablePromiseCancel[] = [];

  protected canceled = false;

  public async cancel(): Promise<void> {
    if (this.canceled) {
      throw new Error('CancelToken was already canceled');
    }

    this.canceled = true;

    if (this.deferred.length) {
      await Promise.all(this.deferred.map(async (queued) => queued()));
    }

    if (this.withQueue.length) {
      await Promise.all(
        this.withQueue.map((cb) => cb())
      );
    }
  }

  public defer(fn: () => Promise<void> | void): void {
    this.deferred.push(fn);
  }

View on GitHub (pinned to 7d981676b3)