/** * 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 }; }