Skip to main content

Handling results

Like fetch, fetchCuple resolves with whatever the server answered, typed as every result the route can send. Check result:

import { fetchCuple } from "@cuple/client";

const res = await fetchCuple(client.getUser.get, { params: { id } });
// ^? success | user-not-found | invalid-params | unexpected-error

if (res.result === "user-not-found") return showNotFound();
if (res.result === "success") showUser(res.user);

It rejects only when there was no answer:

OutcomeRejects with
No answer: offline, DNS, CORS, a proxy's HTML error pageCupleTransportError
signal abortedDOMException (AbortError)

Just the data​

When your code only has something to do with success, keep only that. Anything else rejects as CupleUnexpectedResponseError, with the response, and goes where unhandled errors go:

const { user } = await fetchCuple(client.getUser.get, { params: { id } }).thenKeepSuccess();

Narrowing: keep or reject​

List what your code handles, or what it doesn't. A result that isn't kept rejects as CupleUnexpectedResponseError:

const res = await fetchCuple(client.createUser.post, { body }).thenKeep([
"success",
"invalid-body",
]);
// ^? success | invalid-body

if (res.result === "invalid-body") showIssues(res.issues);
const res = await fetchCuple(client.getUser.get, { params: { id } }).thenReject([
"unexpected-error",
]);
// ^? success | user-not-found | invalid-params
MethodResolves with
(default)every result the server sent
.thenKeepSuccess()success
.thenKeep([...])exactly the listed results; "success" is not implied
.thenKeepAlso([...])what it resolved with so far, plus the listed results
.thenReject([...])every result the server sent, except the listed ones
.thenRejectAlso([...])what it resolved with so far, minus the listed results
.thenKeepAll()everything: every result the server sent, plus transport-error and abort. Only a bug in your own code rejects.

thenKeep and thenReject are complete lists: they start over from the response. The Also methods add to what came before, so they chain: .thenKeepSuccess().thenKeepAlso(["invalid-body"]).

No answer as a value​

  • "transport-error" can be listed like any result, for code that has something to do when the server can't be reached (a form that keeps the user's input). It resolves as { result: "transport-error", statusCode, message }. statusCode is set when something answered, but not in Cuple's format (a gateway page).
  • "abort" can be listed too, for a request you cancel on purpose. It resolves as { result: "abort", statusCode: null, message }.
const res = await fetchCuple(client.search.get, { query, options: { signal } }).thenKeepAlso([
"abort",
]);
if (res.result === "abort") return; // a newer search replaced this one

In React​

useGet gives you success, and { resolveAlso: [...] } lists what the component renders instead. useAction gets whatever your function resolves with: narrow fetchCuple with thenKeep, and whatever it doesn't keep goes to the nearest <Boundary>. See Errors.

Why it works this way

fetchCuple works like fetch: a response is a value, whatever its status, so nothing is thrown at you by surprise. You narrow it when you know what your code handles.

useGet stays success-only: a component suspends until it has data to render, and dealing with an error is an enhancement.

A result you keep is typed in the return value, with its real shape. A try/catch gives you unknown. "transport-error" and "abort" can be listed like any server result, so handling offline or a cancelled request is the same code as handling any other failure.

The lists are chained methods (.thenKeep([...])), not an option of fetchCuple, because of a typing limit.