Go async with Pending
The standard fetch throws on network errors. @antithrow/std re-exports it as a function that returns a Result instead. Asynchronous results have a third shape: Pending.
Fetch the configuration
Swap the entry file over to fetching a remote file. Update src/index.ts:
import { Err, Ok, type Result } from "antithrow";
import { fetch, Response } from "@antithrow/std";
function parsePort(raw: string): Result<number, "invalid-port"> {
const port = Number(raw);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
return new Err("invalid-port");
}
return new Ok(port);
}
const config = await parsePort("8080")
.map((port) => `http://localhost:${port}/config.json`)
.andThen((url) => fetch(url))
.andThen((response) => Response.json(response));
Three things are new:
fetch(url)returns a result whose asynchronous branch isPending. BecausePendingisPromiseLike, the whole chain becomesPendingonce a.andThenreturns one.Response.json(response)reads the body and returns anotherPending. It is safe to chain them becauseandThenthreads errors through.awaiton aPendinghands back a settledOkorErr, soconfighas the typeSettled<unknown, …>at the end.
Inspect the outcome
Log the settled state as usual.
if (config.isOk()) {
console.log("got config:", config.value);
} else if (config.isErr()) {
console.log("failed:", config.error);
}
Start any local server serving a JSON file at http://localhost:8080/config.json and run:
npx tsx src/index.ts
If the server is reachable, you will see the parsed JSON printed. If it is not, you will see the network error — captured, not thrown.
Why Pending and not Promise
Pending<T, E> looks and acts like a promise: you can await it. The difference is that antithrow never rejects the underlying promise. A network failure produces Err(TypeError) that you can branch on, not an exception that bubbles up. That is why this whole pipeline needed no try / catch.