Axios封装实战

2 阅读4分钟

Axios封装实战

引言

在现代Web应用开发中,客户端与服务器的通信是不可或缺的环节,往往通过HTTP请求来实现数据的传输与交互。Axios,作为一个流行的基于Promise的HTTP客户端库,提供丰富的接口供开发者进行网络请求。然而,在大型项目中直接使用Axios可能导致请求代码分散在各处,难以维护和统一处理公共逻辑。因此,对Axios进行封装以适应不同的业务需求和简化请求流程变得尤为重要。在本文中,我们将探讨如何封装Axios以实现一个结构清晰、易于维护的HTTP请求实例。

基础配置

Axios实例的创建始于基础配置的设定。baseConfig 对象中包含了实例的关键配置项:

const { CancelToken } = axios;

// axios基础配置
const baseConfig = {
    API_BASE_URL: "",
    REQUEST_TIMEOUT: 5000,
    cancelTokenSource: CancelToken.source(),
};

API_BASE_URL 确定了所有请求的公共前缀,REQUEST_TIMEOUT 定义了请求的超时时间,而 cancelTokenSource 则用于取消请求。这些基础配置项的设置对于构建一个健壮的HTTP服务至关重要。

请求拦截器

请求拦截器 requestInterceptor 是对发出的HTTP请求进行预处理的先行官:

// 请求拦截器
const requestInterceptor = (config) => {
    if (!navigator.onLine) {
        config.cancelToken = baseConfig.cancelTokenSource.token;
        baseConfig.cancelTokenSource.cancel('no network');
    }
    if (window.loginInfo) {
        const { token, access_token, token_type } = window.loginInfo;
        config.headers.Authorization = token || `${token_type} ${access_token}`;
    }
    config.metadata = { startTime: new Date().getTime() };
    return config;
};

拦截器检测当前浏览器网络状态,若无网络则直接取消请求。若用户已登录,则为请求添加认证header,便于后端验证用户身份。此外,请求开始的时间被记录以供后续性能分析之用。

响应拦截器

响应拦截器 responseInterceptor 主要用于处理服务器返回的响应数据:

// 响应拦截器
const responseInterceptor = (response) => {
    return response.data;
};

该拦截器直接返回了响应中的 data ,省去了在数据处理环节中访问 response.data 的步骤,简化了后续的逻辑。

错误处理

请求失败处理 requestHandleError 和响应失败处理 responseHandleError 是遇到请求异常时的救生索:

// 请求失败处理回调
const requestHandleError = (error) => {
    return Promise.reject(new Error(error.message, { cause: error }));
};

// 响应失败处理回调
const responseHandleError = (error) => {
	if (error.response) {
		const { status, data } = error.response;
		switch (status) {
			case 401:
				// call login again handle
				break;
			case 4010:
				Modal.clear();
				Modal.confirm({
					bodyClassName: "modal-confirm-body",
					content: 'please login first',
					cancelText: 'cancel',
					confirmText: 'login',
					onConfirm: () => void console.log('')
				})
				break;
			default:
				break;
		}
		return Promise.reject(new Error(data.msg || error.message, { cause: error }));
	} else if (error.code === "ECONNABORTED") {
		// 连接超时
		handleConnectError();
		return Promise.reject(new Error('', { cause: error }));
	}
	return Promise.reject(new Error('internael error', { cause: error }));
}

这两个函数负责捕捉请求失败和响应失败的异常情况,并生成包含详细错误信息的Promise对象,使得错误可以在调用链的任何一个步骤被捕获和处理。

超时和网络错误处理

超时或网络异常时触发 handleConnectError 函数,以向用户显示友好的错误信息:

// 超时回调
const handleConnectError = () => {
	const customIcon = () => (
		<div style={{ textAlign: 'center' }}>
			<UndoOutline fontSize={24} />
			<p>{'loading failed'}</p>
		</div>
	);
	Toast.show({
		content: customIcon(),
		duration: 1500,
		afterClose: () => {console.log('close')}
	})
}

创建Axios实例

主函数 createAxiosInstance 将配置和拦截器绑定到新创建的Axios实例:

// 主函数
const createAxiosInstance = (config) => {
	// 设置baseURL 请求头 超时时间等
	const instance = new axios.create({
		baseURL: baseConfig.API_BASE_URL,
		headers: {
			"Content-Type": "application/json"
		},
		timeout: baseConfig.REQUEST_TIMEOUT,
		...config
	})

	// 设置请求头
	instance.defaults.headers.pragma = "no-cache";
	instance.defaults.headers.get["Content-Type"] = "application/json";

	// 设置请求拦截器
	instance.interceptors.request.use(requestInterceptor, requestHandleError);

	// 设置响应拦截器
	instance.interceptors.response.use(responseInterceptor, responseHandleError);

	return instance;
}

通过集成前面提到的基础配置、拦截器和错误处理,实现了对Axios的全面封装。随着该实例的返回,开发者可以使用它进行后续的API请求,享受到统一处理请求和响应的便捷。

完整代码

import React from "react";
import axios from "axios";
import { Modal, Toast } from "antd-mobile";
import { UndoOutline } from 'antd-mobile-icons'

const { CancelToken } = axios;

// axios基础配置
const baseConfig = {
	API_BASE_URL: "",
	REQUEST_TIMEOUT: 5000,
	cancelTokenSource: CancelToken.source(),
}

// 请求拦截器
const requestInterceptor = (config) => {
	if (!navigator.onLine) {
		config.cancelToken = baseConfig.cancelTokenSource.token;
		baseConfig.cancelTokenSource.cancel('no network');
	}
	if (window.loginInfo) {
		const { token, access_token, token_type } = window.loginInfo;
		config.headers.Authorization = token || `${token_type} ${access_token}`;
	}
	config.metadata = { startTime: new Date().getTime() };
	return config;
}

// 响应拦截器
const responseInterceptor = (response) => {
	return response.data;
}

// 请求失败处理回调
const requestHandleError = (error) => {
	return Promise.reject(new Error(error.message, { cause: error }));
}

// 响应失败处理回调
const responseHandleError = (error) => {
	if (error.response) {
		const { status, data } = error.response;
		switch (status) {
			case 401:
				// call login again handle
				break;
			case 4010:
				Modal.clear();
				Modal.confirm({
					bodyClassName: "modal-confirm-body",
					content: 'please login first',
					cancelText: 'cancel',
					confirmText: 'login',
					onConfirm: () => void console.log('')
				})
				break;
			default:
				break;
		}
		return Promise.reject(new Error(data.msg || error.message, { cause: error }));
	} else if (error.code === "ECONNABORTED") {
		// 连接超时
		handleConnectError();
		return Promise.reject(new Error('', { cause: error }));
	}
	return Promise.reject(new Error('internael error', { cause: error }));
}

// 超时回调
const handleConnectError = () => {
	const customIcon = () => (
		<div style={{ textAlign: 'center' }}>
			<UndoOutline fontSize={24} />
			<p>{'loading failed'}</p>
		</div>
	);
	Toast.show({
		content: customIcon(),
		duration: 1500,
		afterClose: () => {console.log('close')}
	})
}

// 主函数
const createAxiosInstance = (config) => {
	// 设置baseURL 请求头 超时时间等
	const instance = new axios.create({
		baseURL: baseConfig.API_BASE_URL,
		headers: {
			"Content-Type": "application/json"
		},
		timeout: baseConfig.REQUEST_TIMEOUT,
		...config
	})

	// 设置请求头
	instance.defaults.headers.pragma = "no-cache";
	instance.defaults.headers.get["Content-Type"] = "application/json";

	// 设置请求拦截器
	instance.interceptors.request.use(requestInterceptor, requestHandleError);

	// 设置响应拦截器
	instance.interceptors.response.use(responseInterceptor, responseHandleError);

	return instance;
}

export default createAxiosInstance;

结语

本文展示了如何将Axios库封装成一个易于维护和使用的HTTP请求工具。通过配置、拦截器和错误处理的合理使用,开发者可以构建出一个强大的,能够提升整个项目开发效率和用户体验的HTTP服务。