Skip to content

fix(api): add timeout and cancellation to apiFetch (#54) - #197

Merged
presidojay1 merged 2 commits into
StellarTickets:mainfrom
boluwacodes:fix/54-apifetch-timeout
Sep 26, 2026
Merged

presidojay1 merged 2 commits into
StellarTickets:mainfrom
boluwacodes:fix/54-apifetch-timeout

Conversation

@boluwacodes

@boluwacodes boluwacodes commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

apiFetch (src/lib/api.ts) called fetch with no timeout and no way for callers to abort. When the backend stalled, buttons stayed on "Loading…" / "Working…" forever.

Changes

src/lib/api.ts

  • RequestOptions gets two optional fields:
    • signal?: AbortSignal, for caller-controlled cancellation such as on unmount;
    • timeoutMs?: number, defaulting to the new exported DEFAULT_TIMEOUT_MS = 15_000. 0 disables the timeout.
  • Timeout: AbortSignal.timeout(timeoutMs) is combined with the caller's signal via AbortSignal.any and passed to fetch.
  • New NetworkError: extends ApiError with status = 0. Existing instanceof ApiError handling, such as actionErrorMessage, therefore shows its friendly message with no call-site changes:
    • timeout: "The server took too long to respond. Please try again."
    • network failure: "Could not reach the server. Check your connection and try again."
  • Caller abort: the original AbortError is rethrown, so a deliberate cancel isn't reported to the user as a network failure.

HTTP error handling (non-2xx → ApiError) and 204 handling are unchanged.

src/lib/api-timeout.test.ts (new)

  • A stalled request times out with a NetworkError (status 0, "took too long").
  • A fetch rejection (TypeError: Failed to fetch) becomes a NetworkError.
  • A caller abort() rethrows the AbortError, not a NetworkError.
  • An AbortSignal is always passed to fetch by default.

Commits

  • fix(api): add timeout and cancellation to apiFetch (#54)
  • test(api): cover apiFetch timeout, network failure and caller abort (#54)

Testing

Not run locally. To check: npm test. It relies on AbortSignal.timeout / AbortSignal.any, which Node 20+ and all current browsers support.
Closes #54
Closes #55
Closes #56
Closes #57

A stalled backend left buttons stuck in 'Loading…/Working…' because fetch
had no timeout and callers couldn't abort.

- RequestOptions gains an optional `signal` and `timeoutMs`
  (default 15s via AbortSignal.timeout, combined with AbortSignal.any).
- Timeouts and network failures throw a new NetworkError (an ApiError with
  status 0), so actionErrorMessage already shows its friendly message.
- A caller-initiated abort rethrows the original AbortError instead of
  being reported as a network failure.
@netlify

netlify Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for stellartickets failed.

Name Link
🔨 Latest commit 7cd22ad
🔍 Latest deploy log https://app.netlify.com/projects/stellartickets/deploys/6ab7fbdbd5b99100085c70d1

@drips-wave

drips-wave Bot commented Sep 26, 2026

Copy link
Copy Markdown

@boluwacodes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@presidojay1
presidojay1 merged commit b99c569 into StellarTickets:main Sep 26, 2026
0 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants