zzyxyz_server/static/fetch-wrapper.js

154 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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