class FetchWrapper { constructor(baseURL, options = {}, contentTypeMappings = {}, onError = console.error) { this.baseURL = baseURL; this.contentTypeMappings = contentTypeMappings; this.defaultContentType = 'application/json'; this.onError = onError; this.defaultOptions = { ...{ method: 'GET', headers: new Headers({ 'Content-Type': this.defaultContentType }), mode: 'cors', // 指定是否允许跨域请求,默认为cors cache: 'default', // 请求的缓存模式 credentials: 'same-origin', // 请求的凭据模式,默认同源请求时发送cookies parseResponseHandler: { 'application/json': response => response.json(), 'text/*': response => response.text(), 'image/*': response => response.blob(), 'application/octet-stream': response => response.blob(), '*/*': response => response.arrayBuffer(), // 默认处理程序 }, ...options } }; } async fetch(url, options = {}) { // 合并默认选项与传入的options const mergedOptions = { ...this.defaultOptions, ...options }; // 根据URL自动设置Content-Type(如果有匹配的映射) const matchedMapping = Object.entries(this.contentTypeMappings).find(([pattern, type]) => url.endsWith(pattern)); if (matchedMapping && !mergedOptions.headers.has('Content-Type')) { mergedOptions.headers['Content-Type'] = matchedMapping[1]; } // 根据请求方法处理数据 if (['POST', 'PUT', 'PATCH'].includes(mergedOptions.method)) { if (typeof mergedOptions.body === 'object') { mergedOptions.body = JSON.stringify(mergedOptions.body); // 如果Content-Type未设置或默认为application/json,则需要在这里设置 if (!mergedOptions.headers.has('Content-Type')) { mergedOptions.headers.set('Content-Type', 'application/json'); } } else if (mergedOptions.body !== null && typeof mergedOptions.body !== 'string') { throw new Error('非GET请求时, body必须是一个对象或字符串'); } } else if (mergedOptions.method === 'GET' && mergedOptions.body) { console.warn('GET请求不支持发送请求体, 已忽略提供的body参数'); delete mergedOptions.body; } url = new URL(url, this.baseURL).href; return fetch(url, mergedOptions) .then(async response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); // console.warn(`HTTP warning! status: ${response.status}`); } const contentType = response.headers.get('content-type').split(';')[0].trim(); const parseResponseHandler = this.getParseResponseHandler(contentType); const responseData = await parseResponseHandler(response); return formatResponse(response, responseData); }) .catch(error => { error.options = mergedOptions; error.url = url; error.timestamp = Date.now(); error.onFunction = 'fetch'; this.onError(error.message); throw error; }); } getParseResponseHandler(contentType) { if (contentType.startsWith('text/')) { return this.defaultOptions.parseResponseHandler['text/*'] || this.defaultOptions.parseResponseHandler['*/*']; } if (contentType.startsWith('image/')) { return this.defaultOptions.parseResponseHandler['image/*'] || this.defaultOptions.parseResponseHandler['*/*']; } return this.defaultOptions.parseResponseHandler[contentType] || this.defaultOptions.parseResponseHandler['*/*']; } async fetchWithRetry(url, options = {}, maxRetries = 3, retryDelayBaseMs = 1000) { let remainingAttempts = maxRetries; const delays = []; for (let i = 0; i < maxRetries; i++) { delays.push(retryDelayBaseMs * Math.pow(2, i)); } const attemptFetch = async (retryIndex = 0) => { try { return await this.fetch(url, options); } catch (error) { if (remainingAttempts > 1) { console.log(`请求失败,剩余重试次数:${remainingAttempts - 1}`); setTimeout(() => attemptFetch(retryIndex + 1), delays[retryIndex]); remainingAttempts--; return; } else { this.onError(error.message); throw error; } } }; return attemptFetch(); } // 可以为不同的HTTP方法提供便捷的方法,如: async post(url, body, options = {}) { return this.fetch(url, { ...options, method: 'POST', body }); } async get(url, options = {}) { return this.fetch(url, { ...options, method: 'GET' }); } async put(url, body, options = {}) { return this.fetch(url, { ...options, method: 'PUT', body }); } async patch(url, body, options = {}) { return this.fetch(url, { ...options, method: 'PATCH', body }); } async delete(url, options = {}) { return this.fetch(url, { ...options, method: 'DELETE' }); } }; function formatResponse(response, data) { return { status: response.status, message: response.statusText, data, }; } // 检查是否在Node.js环境中 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { // Node.js环境,使用CommonJS module.exports = FetchWrapper; } else if (typeof window !== 'undefined') { // 浏览器环境,直接暴露到全局作用域(不推荐,但简单示例) // window.FetchWrapper = FetchWrapper; }