| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- /**
- * utils/http.js —— 唯一的网络出口(只有 services 调它;视图层不直接 fetch)。
- * 只带公开的 App ID;任何凭据都不进前端。
- */
- export const request = (...args) => fetch(...args);
- export class HttpError extends Error {
- constructor(status, body, url) {
- super(`HTTP ${status}${body ? ` · ${body}` : ''}`);
- this.name = 'HttpError';
- this.status = status;
- this.url = url;
- }
- }
- export function createHttp({ base, appId, fetchImpl = (...a) => fetch(...a) }) {
- const headers = () => ({ 'X-Parse-Application-Id': appId, Accept: 'application/json' });
- async function call(path, { method = 'GET', body, timeoutMs = 20000, retries = 1 } = {}) {
- const url = base + path;
- let lastErr;
- for (let i = 0; i <= retries; i++) {
- const ctrl = new AbortController();
- const t = setTimeout(() => ctrl.abort(), timeoutMs);
- try {
- const res = await fetchImpl(url, {
- method,
- headers: { ...headers(), ...(body ? { 'Content-Type': 'application/json' } : {}) },
- body: body ? JSON.stringify(body) : undefined,
- cache: 'no-store',
- signal: ctrl.signal,
- });
- const text = await res.text();
- let json = null;
- try { json = text ? JSON.parse(text) : {}; } catch { json = null; }
- if (!res.ok) throw new HttpError(res.status, json?.error || text.slice(0, 200), path);
- if (json == null) throw new HttpError(res.status, '返回的不是 JSON', path);
- return json;
- } catch (err) {
- lastErr = err;
- // 4xx 不重试(重试也没用);网络错误 / 5xx 重试一次
- if (err instanceof HttpError && err.status < 500) break;
- if (i < retries) await new Promise((r) => setTimeout(r, 400 * (i + 1)));
- } finally {
- clearTimeout(t);
- }
- }
- throw lastErr;
- }
- return {
- get: (path, opts) => call(path, opts),
- post: (path, body, opts) => call(path, { ...opts, method: 'POST', body }),
- put: (path, body, opts) => call(path, { ...opts, method: 'PUT', body }),
- };
- }
- /**
- * 分页拉全表:每页 limit 条(Parse 单次上限 1000),直到不足一页。
- * 翻了 50 页仍满页 → 标记 truncated,UI 明确提示「数据被截断」,不悄悄少显示。
- */
- export async function fetchAll(http, className, { limit = 1000, order = 'createdAt' } = {}) {
- const rows = [];
- let skip = 0;
- for (let guard = 0; guard < 50; guard++) {
- const json = await http.get(`/classes/${encodeURIComponent(className)}?limit=${limit}&skip=${skip}&order=${order}`);
- const batch = Array.isArray(json.results) ? json.results : [];
- rows.push(...batch);
- if (batch.length < limit) return { rows, truncated: false };
- skip += limit;
- }
- return { rows, truncated: true };
- }
|