axios 封装
# Axios 封装详解
为了创建一个功能强大且易于管理的HTTP请求库,我们基于`axios`进行了扩展和优化。以下是对这段代码的详细解释,帮助你一步步了解其工作原理。
## 1. 引入依赖
首先,我们从`axios`库导入必要的模块:
import axios from 'axios';
2. 定义全局常量
定义了一个全局常量MAX_RETRY
用于设置重试次数的最大值:
const MAX_RETRY = 3;
3. 创建 Axios 实例
通过axios.create()
创建了一个自定义配置的Axios实例,并设置了基础URL、超时时间和默认头部信息:
const service = axios.create({ baseURL: process.env.NEXT_PUBLIC_SCRATCH_API_URL, // API 的 base_url timeout: 5000, // 请求超时时间 headers: { 'Content-Type': 'application/json', }
});
4. 初始化 Custom 属性
确保service.defaults.custom
属性存在,以便后续存储与特定请求关联的AbortController
实例:
if (!service.defaults.custom) service.defaults.custom = {};
5. 请求拦截器
在发送请求之前,通过请求拦截器为每个请求添加AbortController
信号,并检查是否需要附加认证令牌:
service.interceptors.request.use( config => { if (!config.signal) { const controller = new AbortController(); config.controller = controller;
config.signal = controller.signal;
}
if (config.useToken !== false) { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; }
}
return config; },
error => Promise.reject(error) );
6. 响应拦截器
响应拦截器负责处理请求成功后的数据返回以及错误处理逻辑,包括超时、网络错误和其他状态码(如500、408)导致的重试机制:
service.interceptors.response.use( response => response.data, async error => { const originalRequest = error.config;
// 处理超时错误和其他类型的网络错误 if ((error.code === 'ECONNABORTED' && error.message.includes('timeout')) || !error.response) { return Promise.reject(new Error('请求超时或网络错误,请稍后重试')); }
// 实现重试逻辑,最多重试3次 if ([500, 408].includes(error.response?.status)) { if (!originalRequest._retry) { originalRequest._retry = true; originalRequest._retryCount = originalRequest._retryCount || 0;
if (originalRequest._retryCount < MAX_RETRY) { originalRequest._retryCount++;
// 使用指数退避算法添加延迟 await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, originalRequest._retryCount))); return service(originalRequest); }
}
}
return Promise.reject(error); }
);
7. 取消请求函数
提供了一个cancelRequest
函数来取消特定请求。它通过提供的键查找对应的AbortController
并调用其abort
方法:
export const cancelRequest = (key) => { if (service.defaults.custom[key] && service.defaults.custom[key].controller) { service.defaults.custom[key].controller.abort();
delete service.defaults.custom[key]; }
};
8. HTTP 方法抽象
为了简化代码并提高一致性,我们将GET、POST、PUT和DELETE方法的创建抽象成了一个通用函数createHttpMethod
。这个函数接受HTTP方法名称作为参数,并根据传入的数据类型(查询参数或请求体)正确地构造请求:
const createHttpMethod = (method) => (url, dataOrParams, config = {}) => { const controller = new AbortController(); config.signal = controller.signal;
config.controller = controller;
if (config.requestKey) { service.defaults.custom[config.requestKey] = { controller };
}
switch (method.toLowerCase()) { case 'get': return service.get(url, { params: dataOrParams, ...config }); case 'post': return service.post(url, dataOrParams, config); case 'put': return service.put(url, dataOrParams, config); case 'delete': return service.delete(url, { data: dataOrParams, ...config }); default: throw new Error(`Unsupported HTTP method: ${method}`); }
};
export const GET = createHttpMethod('GET'); export const POST = createHttpMethod('POST'); export const PUT = createHttpMethod('PUT'); export const DELETE = createHttpMethod('DELETE');
9. 文件上传方法
专门定义了UPLOAD
方法用于文件上传,该方法允许指定进度回调函数以跟踪上传进度:
export const UPLOAD = (url, data, onUploadProgress) => { return service.post(url, data, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress
});
};
通过上述步骤,我们构建了一个灵活且强大的axios封装,它不仅能够处理常见的HTTP操作,还支持请求取消、错误处理和自动重试等功能,从而提高了应用程序的健壮性和用户体验。
import axios from 'axios';
// 错误重试次数 const MAX_RETRY = 3;
// 创建axios实例 const service = axios.create({ baseURL: process.env.NEXT_PUBLIC_SCRATCH_API_URL, // API 的 base_url timeout: 5000, // 请求超时时间 headers: { 'Content-Type': 'application/json', }
});
// 初始化custom属性用于存储AbortController if (!service.defaults.custom) service.defaults.custom = {};
// 请求拦截器 service.interceptors.request.use(
config => { if (!config.signal) { const controller = new AbortController(); config.controller = controller;
config.signal = controller.signal;
}
if (config.useToken !== false) { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; }
}
return config; },
error => Promise.reject(error) );
// 响应拦截器 service.interceptors.response.use(
response => response.data, async error => { const originalRequest = error.config;
// 处理超时错误和其他类型的网络错误 if ((error.code === 'ECONNABORTED' && error.message.includes('timeout')) || !error.response) { return Promise.reject(new Error('请求超时或网络错误,请稍后重试')); }
// 实现重试逻辑,最多重试3次 if ([500, 408].includes(error.response?.status)) { if (!originalRequest._retry) { originalRequest._retry = true; originalRequest._retryCount = originalRequest._retryCount || 0;
if (originalRequest._retryCount < MAX_RETRY) { originalRequest._retryCount++;
// 使用指数退避算法添加延迟 await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, originalRequest._retryCount))); return service(originalRequest); }
}
}
return Promise.reject(error); }
);
// 导出取消请求函数 export const cancelRequest = (key) => { if (service.defaults.custom[key] && service.defaults.custom[key].controller) { service.defaults.custom[key].controller.abort();
delete service.defaults.custom[key]; }
};
// 定义HTTP方法 const createHttpMethod = (method) => (url, dataOrParams, config = {}) => { const controller = new AbortController(); config.signal = controller.signal;
config.controller = controller;
if (config.requestKey) { service.defaults.custom[config.requestKey] = { controller };
}
switch (method.toLowerCase()) { case 'get': return service.get(url, { params: dataOrParams, ...config }); case 'post': return service.post(url, dataOrParams, config); case 'put': return service.put(url, dataOrParams, config); case 'delete': return service.delete(url, { data: dataOrParams, ...config }); default: throw new Error(`Unsupported HTTP method: ${method}`); }
};
export const GET = createHttpMethod('GET'); export const POST = createHttpMethod('POST'); export const PUT = createHttpMethod('PUT'); export const DELETE = createHttpMethod('DELETE');
// 文件上传方法 export const UPLOAD = (url, data, onUploadProgress) => { return service.post(url, data, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress
});
};