统一盲盒前端 API 调用层并收敛错误处理
This commit is contained in:
@@ -12,6 +12,89 @@ export class FetchError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user