Ant Design Pro 5 网络请求和错误处理

2020-11-29 19:04 阅读

Ant Design Pro 5 的网络请求有点复杂,只看文档不阅读源码搞不清楚到底发生了什么事情。涉及到fetch umi-request @umijs/plugin-request Ant Design Pro 5相关代码。

fetch

代替XMLHttpRequest的访问和操纵HTTP的技术。fetch最大的问题是,只有在发生网络故障时或请求被阻止时,才抛出异常;而对于 HTTP 状态码为 404 或 500 之类的情况,会认为是正常的响应,并不会抛出异常。

参考资料:Fetch API

umi-request

基于 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

@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

安装前面的数据格式约定,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 ...
  }
});

参考资料:

Ant Design Pro 5

提供了自定义错误处理方法,和自定义数据格式的接口。默认的演示代码中,覆盖了@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,
};
咨询
交流群
电话