theme: orange highlight: an-old-hope
引言
这一系列文章记录了我多年工作中积累的心得体会,旨在与大家分享经验,欢迎各位大佬指导,共同提升技术水平。
承接上篇
前文《后台经验系列(一)--网络封装(上)》已经介绍了网络请求封装的基本框架,接下来我们将深入探讨请求的生命周期及错误处理。
代码仓库在 案例一:网络封装 · Duck/EmpiricalCase - 码云 - 开源中国 (gitee.com),可以下载下来看完整代码
一、请求生命周期
在完善功能之前,让我们先来探讨请求的生命周期。
graph LR
发起请求 --> 请求拦截器 --> 实际请求 --> 响应拦截器 --> 请求成功 --> 业务代码
- 发起请求:业务代码发起请求。
- 请求拦截器:在实际发出请求之前,对请求进行校验、参数处理、请求头处理等操作,如果存在不符合逻辑的请求,可以直接取消。
- 实际请求:发出实际的网络请求。
- 响应拦截器:处理请求成功或失败后返回的数据,包括错误处理、数据校验等。
- 业务代码:处理请求并获得响应数据,继续业务逻辑。
针对以上生命周期,我们可以封装如下函数:
const baseUrl = 'http://localhost:8080';
function ajaxFunc({ url, type = 'get', data, headers } = config) {
const realUrl = baseUrl + url;
return new Promise((resolve, reject) => {
// 请求拦截器
if (requestInterceptor(config) === false) {
return;
}
$.ajax({
url: realUrl,
type,
data,
success: function (responseData) {
// 响应拦截器:请求成功
const result = responseInterceptorSuccess(responseData);
resolve(result);
},
error: function (response) {
// 响应拦截器:请求失败
const result = responseInterceptorError(response);
reject(result);
},
});
});
}
/**
- 请求拦截器
- @param {Object} config
- @return false 表示取消请求
*/
function requestInterceptor(config) {}
/**
- 响应拦截器:请求成功,返回2xx的code
- @param {Object} response
*/
function responseInterceptorSuccess(responseData) {}
/**
- 响应拦截器:请求失败,即返回除了2xx外的code
@param {Object} response
*/
function responseInterceptorError(response) {}
二、错误处理-响应拦截器
封装网络请求的一个重要作用是统一处理错误。我将错误分为两类:网络状态码错误和业务逻辑错误。
- 网络状态码错误:如404,403,500等
- 业务逻辑错误:如1000代表权限错误、10001代表关键词错误等,但是statusCode是2xx的,代表网络是成功的
1. 响应拦截器:请求错误拦截处理
在处理网络请求时,我们需要对网络状态码进行拦截处理,例如处理离线、403等情况。 我们先在request文件夹下新建文件error.js,用来存储各种网络请求错误的处理代码
// src/utils/request/error.js /** * 没网的处理逻辑 */ function handleOffline(error) { Object.assign(error, { code: 0, message: '网络错误', }); // 消息提醒,比如element的 message.error() console.log(error); }
/**
- 403的处理逻辑
*/
function handle403(error) {
Object.assign(error, {
code: 403,
message: ‘权限出错,请返回登录页面重新登录’,
});
// 消息提醒,比如element的 message.error()
console.log(error);
// 清除缓存,跳转登录页
}
然后返回网络请求的index.js文件内,在 响应拦截器:请求错误拦截处理的函数内,添加一些代码
/** * 执行AJAX请求 * @param {string} url 请求的URL * @param {string} type 请求类型(如GET、POST等) * @param {Object|string} data 要发送的数据 * @returns {Promise} 返回一个Promise对象,成功时resolve响应结果,失败时reject错误对象 */ const baseUrl = 'http://localhost:8080'; function ajaxFunc(config) { const { url, type = 'get', data, headers } = config; const realUrl = baseUrl + url; return new Promise((resolve, reject) => { // 请求拦截器 if (requestInterceptor(config) === false) { return; } $.ajax({ url: realUrl, type, data, success: function (res) { // 响应拦截器:请求成功 const result = responseInterceptorSuccess(res); resolve(result); }, error: function (err) { // 响应拦截器:请求失败 responseInterceptorError(err); // 把响应数据返回出去,页面如果要处理,可以获取 reject(err); }, }); }); }
/**
- 请求拦截器
- @param {Object} config
- @return false 表示取消请求
*/
function requestInterceptor(config) {
return config;
}/**
- 响应拦截器:请求成功,返回2xx的code
- @param {Object} response
*/
function responseInterceptorSuccess(responseData) {
return responseData;
}/**
- 响应拦截器:请求失败,即返回除了2xx外的code
- @param {Object} response
*/
function responseInterceptorError(response) {
const httpCode = response.status;
const error = {
type: ‘http’,
code: httpCode,
message: ‘请求出错’,
};
if (!window.navigator.onLine) {
// 网路错误
handleOffline(error);
return;
}
if (httpCode === 403) {
handle403(error);
return;
}
// 后面可以接上其他的处理逻辑
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(error);
}
我们看看效果,我自己用express写了一个服务器,写了一个接口固定返回403
2. 响应拦截器:请求成功拦截处理
除了处理网络状态码错误外,我们还需要处理业务逻辑错误,如权限错误、关键词错误等。
我们在 error.js 文件里面新增逻辑code错误的处理代码
/**
* 敏感词的处理逻辑
*/
function handle1002(error) {
Object.assign(error, {
code: 1002,
message: error.message,
});
// 消息提醒,比如element的 message.error()
console.log(error);
// 清除缓存,跳转登录页
}
然后返回网络请求的index.js文件内,在 响应拦截器:请求成功拦截处理的函数内,添加一些代码
// ajax的回调success里面,修改一下代码
success: async function (responseData) {
// 响应拦截器:请求成功
try {
const result = await responseInterceptorSuccess(responseData);
resolve(result);
} catch (error) {
reject(error);
}
},
/**
- 响应拦截器:请求成功,返回2xx的code
@param {Object} response
*/
function responseInterceptorSuccess(responseData) {
if (responseData.code === 0) {
return Promise.resolve(responseData);
}
if (responseData.code === 1001) {
handle1002(responseData);
return Promise.reject(responseData);
}
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(responseData);
return Promise.reject(responseData);
}
在业务代码没变的情况下,我们便完成了这次错误的处理,如图
那么业务代码完成可以去除错误的处理,那么代码便会特别精简 async function a() {
const res = await ajaxFunc({
url: '/codeError',
type: 'get',
data: {
name: 'zhangsan',
age: 18,
},
});
console.log('业务代码--请求成功返回:', res);
}
3. 兼容文件流
当然了,接口不一定都是返回json格式,还有可能会有文件流,比如导出接口、验证码接口等,我们需要对这些接口进行兼容,只需要很简单的两行代码即可 在 responseInterceptorSuccess 函数里面,新增
/**
* 响应拦截器:请求成功,返回2xx的code
* @param {Object} response
*/
function responseInterceptorSuccess(responseData) {
// 判断请求是否一个文件流
if (typeof responseData !== 'object') {
return Promise.resolve(responseData);
}
// 成功
if (responseData.code === 0) {
return Promise.resolve(responseData);
}
// 逻辑报错
if (responseData.code === 1001) {
handle1002(responseData);
return Promise.reject(responseData);
}
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(responseData);
return Promise.reject(responseData);
}
完整代码
通过上述的错误处理机制,我们可以将错误统一处理,使得业务代码更加简洁清晰,提高了代码的可维护性和稳定性。
我这里用的是普通的html+js,如果用的是vue等脚手架,请手动引入文件依赖 import { } from
// src/utils/request/error.js
/**
* 没网的处理逻辑
*/
function handleOffline(error) {
Object.assign(error, {
code: 0,
message: '网络错误',
});
// 消息提醒,比如element的 message.error()
console.log(error);
}
/**
- 403的处理逻辑
*/
function handle403(error) {
Object.assign(error, {
code: 403,
message: ‘权限出错,请返回登录页面重新登录’,
});
// 消息提醒,比如element的 message.error()
console.log(error);
// 清除缓存,跳转登录页
}
/**
敏感词的处理逻辑
*/
function handle1002(error) {
Object.assign(error, {
code: 1002,
message: error.message,
});
// 消息提醒,比如element的 message.error()
console.log(error);
// 清除缓存,跳转登录页
}
// src/utils/request/index.js
/**
* 执行AJAX请求
* @param {string} url 请求的URL
* @param {string} type 请求类型(如GET、POST等)
* @param {Object|string} data 要发送的数据
* @returns {Promise} 返回一个Promise对象,成功时resolve响应结果,失败时reject错误对象
*/
const baseUrl = 'http://localhost:8080';
function ajaxFunc(config) {
const { url, type = 'get', data, headers } = config;
const realUrl = baseUrl + url;
return new Promise((resolve, reject) => {
// 请求拦截器
if (requestInterceptor(config) === false) {
return;
}
$.ajax({
url: realUrl,
type,
data,
success: async function (responseData) {
// 响应拦截器:请求成功
try {
const result = await responseInterceptorSuccess(responseData);
resolve(result);
} catch (error) {
reject(error);
}
},
error: function (response) {
// 响应拦截器:请求失败
responseInterceptorError(response);
// 把响应数据返回出去,页面如果要处理,可以获取
reject(response.responseJSON);
},
});
});
}
/**
* 请求拦截器
* @param {Object} config
* @return false 表示取消请求
*/
function requestInterceptor(config) {
return config;
}
/**
* 响应拦截器:请求成功,返回2xx的code
* @param {Object} response
*/
function responseInterceptorSuccess(responseData) {
// 判断请求是否一个文件流
if (typeof responseData !== 'object') {
return Promise.resolve(responseData);
}
// 成功
if (responseData.code === 0) {
return Promise.resolve(responseData);
}
// 逻辑报错
if (responseData.code === 1001) {
handle1002(responseData);
return Promise.reject(responseData);
}
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(responseData);
return Promise.reject(responseData);
}
/**
* 响应拦截器:请求失败,即返回除了2xx外的code
* @param {Object} response
*/
function responseInterceptorError(response) {
const httpCode = response.status;
const error = {
type: 'http',
code: httpCode,
message: '请求出错',
};
if (!window.navigator.onLine) {
// 网路错误
handleOffline(error);
return;
}
if (httpCode === 403) {
handle403(error);
return;
}
// 后面可以接上其他的处理逻辑
// 最后统一处理
// 消息提醒,比如element的 message.error()
console.log(error);
}
使用
async function a() { const res = await ajaxFunc({ url: '/codeError', type: 'get', data: { name: 'zhangsan', age: 18, }, }); console.log('业务代码--请求成功返回:', res); }
a(); </script>
这是一个从 https://juejin.cn/post/7368761530383728659 下的原始话题分离的讨论话题