Ant Design Pro 5 的网络请求有点复杂,只看文档不阅读源码搞不清楚到底发生了什么事情。涉及到fetch
umi-request
@umijs/plugin-request
Ant Design Pro 5
相关代码。
代替XMLHttpRequest
的访问和操纵HTTP
的技术。fetch最大的问题是,只有在发生网络故障时或请求被阻止时,才抛出异常;而对于 HTTP 状态码为 404 或 500 之类的情况,会认为是正常的响应,并不会抛出异常。
参考资料:Fetch API
基于 fetch 封装,提供诸如缓存、超时、字符编码处理、错误处理等功能。其中错误处理与开发紧密关联。
为了解决fetch在非成功状态码下,不抛出异常的情况,umi-request会判断HTTP状态码是否为2xx
;如果不是,则会抛出异常。并且对fetch自己抛出的异常也进行了处理,加上了更多信息,便于后续的处理。
/src/middleware/parseResponse.js
...
.then(body => {
...
if (copy.status >= 200 && copy.status < 300) {
// 提供源response, 以便自定义处理
if (getResponse) {
ctx.res = { data: body, response: copy };
return;
}
ctx.res = body;
return;
}
throw new ResponseError(copy, 'http error', body, req, 'HttpError');
})
.catch(e => {
if (e instanceof RequestError || e instanceof ResponseError) {
throw e;
}
// 对未知错误进行处理
const { req, res } = ctx;
e.request = e.request || req;
e.response = e.response || res;
e.type = e.type || e.name;
e.data = e.data || undefined;
throw e;
});
...
参考资料:umi-request
@umijs/plugin-request
则在umi-request
上又进行了封装。umi-request
提供了错误处理机制,@umijs/plugin-request
则提供了常规的错误处理方法,并约定了服务器返回响应的数据格式。还提供了useRequest
。
需要特别注意的是,引用request
时,要引用@umijs/plugin-request
,而不是umi-request
,否则会发现request
的行为与@umijs/plugin-request
文档描述的不一致。具体代码如下:
// 引用 umi-request 的 request。错误!!!
import request from 'umi-request';
// 引用 @umijs/plugin-request 的 reqeust。正确。
import {request} from 'umi';
约定服务器返回的数据格式如下:
interface ErrorInfoStructure {
success: boolean; // if request is success
data?: any; // response data
errorCode?: string; // code for errorType
errorMessage?: string; // message display to user
showType?: number; // error display type: 0 silent; 1 message.warn; 2 message.error; 4 notification; 9 page
traceId?: string; // Convenient for back-end Troubleshooting: unique request ID
host?: string; // onvenient for backend Troubleshooting: host of current access server
}
对于服务器返回的数据于这个格式不一致的,可以在Ant Design Pro 5/src/app.tsx
通过配置进行适配。但此适配只作用于错误处理,并影响接口返回的数据。所以在使用useRequest
时,这个适配并没有用。
export const request: RequestConfig = {
errorConfig: {
adaptor: (resData) => {
return {
...resData,
success: resData.ok,
errorMessage: resData.message,
};
},
},
};
返回的数据格式中有success
字段,用于描述请求是否成功。而umi-request
对于http 2xx
以外的响应都会抛出异常,此处的success
又有何用呢?答案是http 2xx
的请求也可以返回success:false
从而人为的抛出一个异常。
但我们并不建议这样做,错误的请求应该返回http 2xx
以外的响应,由umi-request
自动抛出一个异常。所以适配数据结构时,success可始终为true。
/umijs/plugins/blob/master/packages/plugin-request/src/request.ts
...
const errorAdaptor = requestConfig.errorConfig?.adaptor || (resData => resData);
...
// 中间件统一错误处理
// 后端返回格式 { success: boolean, data: any }
// 按照项目具体情况修改该部分逻辑
requestMethodInstance.use(async (ctx, next) => {
...
const errorInfo = errorAdaptor(resData, ctx);
if (errorInfo.success === false) {
// 抛出错误到 errorHandler 中处理
const error: RequestError = new Error(errorInfo.errorMessage);
error.name = 'BizError';
error.data = resData;
error.info = errorInfo;
throw error;
}
});
如服务器按照格式返回数据,则一切较为顺利。但即使是正常的响应,也必须把数据包装在data
字段里,这样在后端和前端都会感觉有些别扭。一般希望正常响应直接返回数据,只有在发生错误是,才返回类似的数据格式。
错误处理时,也会调用上述的数据格式适配,所以并不是success不为false数据适配就无用了。主要从数据适配里获得showType
errorMessage
errorCode
。
/umijs/plugins/blob/master/packages/plugin-request/src/request.ts
const errorAdaptor = requestConfig.errorConfig?.adaptor || (resData => resData);
requestMethodInstance = extend({
errorHandler: (error: RequestError) => {
// @ts-ignore
if (error?.request?.options?.skipErrorHandler) {
throw error;
}
let errorInfo: ErrorInfoStructure | undefined;
if (error.name === 'ResponseError' && error.data && error.request) {
const ctx: Context = {
req: error.request,
res: error.response,
};
errorInfo = errorAdaptor(error.data, ctx);
error.message = errorInfo?.errorMessage || error.message;
error.data = error.data;
error.info = errorInfo;
}
errorInfo = error.info;
if (errorInfo) {
const errorMessage = errorInfo?.errorMessage;
const errorCode = errorInfo?.errorCode;
const errorPage =
requestConfig.errorConfig?.errorPage || DEFAULT_ERROR_PAGE;
switch (errorInfo?.showType) {
case ErrorShowType.SILENT:
// do nothing
break;
case ErrorShowType.WARN_MESSAGE:
message.warn(errorMessage);
break;
case ErrorShowType.ERROR_MESSAGE:
message.error(errorMessage);
break;
case ErrorShowType.NOTIFICATION:
notification.open({
message: errorMessage,
});
break;
case ErrorShowType.REDIRECT:
// @ts-ignore
history.push({
pathname: errorPage,
query: { errorCode, errorMessage },
});
// redirect to error page
break;
default:
message.error(errorMessage);
break;
}
} else {
message.error(error.message || 'Request error, please retry.');
}
throw error;
},
...requestConfig,
});
数据类型中showType
字段的值有以下几个,配合错误处理方法,可以针对不同错误类型以不同的方式显示。
export enum ErrorShowType {
SILENT = 0, // 不提示错误
WARN_MESSAGE = 1, // 警告信息提示
ERROR_MESSAGE = 2, // 错误信息提示
NOTIFICATION = 4, // 通知提示
REDIRECT = 9, // 页面跳转
}
安装前面的数据格式约定,useRequest
默认从响应数据里的data
字段里获取数据。如果服务器端是直接返回数据,则需要在Ant Design Pro/config/config.ts
配置:
request: {
dataField: '',
}
需要注意,前面做的数据适配在useRequest
里是无效的,不要认为做了数据适配就万事大吉了。当然服务器按照前面约定的数据格式返回数据就没这些烦恼了。
Ant Design对分页数据的结构也有规定。
{
list: [],
current?: number,
pageSize?: number,
total?: number,
}
如不符合这个格式,可以在useRequest的formatResult进行配置。如:
const { data, loading } = useRequest(() => {
return services.getUserList('/api/test');
},{
formatResult: (result) => {
return ...
}
});
参考资料:
提供了自定义错误处理方法,和自定义数据格式的接口。默认的演示代码中,覆盖了@umijs/plugin-request
的错误处理方法。如未注意到这点,则会迷惑于Ant Design Pro 5
的错误处理行为与@umijs/plugin-request
文档描述的并不一致,前面所述的错误显示类型
完全无效。
Ant Design Pro/config/config.ts
const errorHandler = (error: ResponseError) => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
}
if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
throw error;
};
export const request: RequestConfig = {
errorHandler,
};