# Results, Errors, and Cancellation

> Results, Errors, and Cancellation at the Askr server boundary, with validated input and explicit failure states.

Source: [https://askrjs.com/docs/http-contracts/results-and-errors](https://askrjs.com/docs/http-contracts/results-and-errors)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

Pass AbortSignal through the client call, distinguish typed problem responses from transport failures, and do not retry cancellations.

```tsx
const controller = new AbortController();
const result = await api.projects.get({ params: { id }, signal: controller.signal });

if (!result.ok) return <ProjectError problem={result.error} />;
return <ProjectDetails project={result.data} />;
```

## Result handling

Every client and createFetch() call returns a FetchResult instead of throwing. Check result.ok first, or switch on result.kind — "success", "http", "request", "network", "timeout", "abort", "decode", or "middleware" — to route to the right handling. unwrap(result) is there for call sites that prefer exceptions; it throws a FetchError carrying the original result on its .result property.

## Problem responses

Any status code you registered with .errors() on an endpoint decodes into a typed HttpResult: ok is false, kind is "http", status matches the response, and error is the value your error codec parsed out of the body. That means a 404 or 422 response is available as typed data on the result, not as something you have to catch or re-parse from a generic error object.

## Abort signals

Pass a signal on an individual call to compose your own AbortController with the client's cancellation handling — the request is aborted if either signal fires. Cancellation initiated by the caller resolves with kind: "abort", while a request that exceeds its timeout resolves with kind: "timeout", so the two are distinguishable without inspecting the abort reason yourself.

## Retries

Set timeout on the client or on an individual call to bound how long a request waits before failing with kind: 'timeout'. Actual retry behavior comes from @askrjs/fetch/middleware's retry() middleware, which by default retries GET, HEAD, PUT, DELETE, and OPTIONS on 408, 425, 429, 500, 502, 503, and 504, honoring a Retry-After header when the server sends one. POST is excluded by default because it isn't in that default method list, not because of its body — a request with a buffered body (PUT included) is cloned and retried normally; only a streaming (ReadableStream) body is skipped, since the middleware can't safely replay a stream it's already consumed.

## Documentation navigation

[Previous](https://askrjs.com/docs/http-contracts/typed-clients/index.md) | [Next](https://askrjs.com/docs/http-contracts/codecs/index.md)
