const pendingReads = new Map(); async function request(path, options) { const binaryBody = options.body instanceof ArrayBuffer || options.body instanceof Blob || options.body instanceof FormData; const response = await fetch(path, { credentials: 'same-origin', headers: { ...(options.body && !binaryBody ? { 'Content-Type': 'application/json' } : {}), ...options.headers }, ...options, body: options.body && typeof options.body !== 'string' && !binaryBody ? JSON.stringify(options.body) : options.body }); const type = response.headers.get('content-type') || ''; const data = type.includes('application/json') ? await response.json() : await response.text(); if (!response.ok) { const error = new Error(data?.message || '操作未完成,请稍后重试'); error.status = response.status; throw error; } return data; } export function api(path, options = {}) { const method = String(options.method || 'GET').toUpperCase(); if (method !== 'GET' || options.body != null || options.signal) return request(path, options); // A quick double click or repeated render must not download and parse the // same large JSON response more than once while the first request is active. const key = String(path); if (pendingReads.has(key)) return pendingReads.get(key); const loading = request(path, options).finally(() => { if (pendingReads.get(key) === loading) pendingReads.delete(key); }); pendingReads.set(key, loading); return loading; }