Files
no-whatever/src/lib/fetcher.ts
T

108 lines
2.5 KiB
TypeScript

/**
* Shared SWR fetcher with standard error handling.
*/
export class FetchError extends Error {
constructor(
message: string,
public status: number,
) {
super(message);
this.name = "FetchError";
}
}
export class ApiRequestError extends Error {
constructor(
message: string,
public status: number,
public payload?: unknown,
) {
super(message);
this.name = "ApiRequestError";
}
}
function extractErrorMessage(payload: unknown): string | null {
if (
payload &&
typeof payload === "object" &&
"error" in payload &&
typeof payload.error === "string"
) {
return payload.error;
}
if (typeof payload === "string" && payload.trim()) {
return payload;
}
return null;
}
async function parsePayload(res: Response): Promise<unknown> {
if (res.status === 204) return undefined;
if (typeof res.text === "function") {
const text = await res.text();
if (!text) return undefined;
try {
return JSON.parse(text);
} catch {
return text;
}
}
if (typeof res.json === "function") {
try {
return await res.json();
} catch {
return undefined;
}
}
return undefined;
}
type RequestJsonInit<TBody> = Omit<RequestInit, "body"> & {
body?: TBody;
};
/**
* Shared JSON requester for imperative client-side API calls.
* - Parses JSON/text payloads automatically
* - Throws ApiRequestError with normalized message on non-2xx
*/
export async function requestJson<TResponse = unknown, TBody = unknown>(
url: string,
init: RequestJsonInit<TBody> = {},
): Promise<TResponse> {
const { body, headers, ...rest } = init;
const hasBody = body !== undefined;
const mergedHeaders = new Headers(headers);
if (hasBody && !mergedHeaders.has("Content-Type")) {
mergedHeaders.set("Content-Type", "application/json");
}
const res = await fetch(url, {
...rest,
headers: mergedHeaders,
body: hasBody ? JSON.stringify(body) : undefined,
});
const payload = await parsePayload(res);
if (!res.ok) {
const message = extractErrorMessage(payload) ?? "请求失败";
throw new ApiRequestError(message, res.status, payload);
}
return payload as TResponse;
}
export async function fetcher<T = unknown>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) {
throw new FetchError(
res.status === 404 ? "NOT_FOUND" : res.status === 401 ? "UNAUTHORIZED" : "FETCH_ERROR",
res.status,
);
}
return res.json();
}