[umi 那些事] 咦,我的数据怎么不对劲

305 阅读2分钟

竞态请求 已经是一个老生常谈的问题,举个例子, 有个日志表格,用户可以点击详情来获取这条日志的详细信息,用户首先点击了 1806 这条数据,请求发送后还没从服务器响应的时候,又点击了 1804 这条数据,这时候 第一次请求 的数据返了回来,用户便看到了 1806 的数据,这便发生了所谓的 请求竞争 现象,于是,用户给你报了一个严重级别的 BUG upload_sf8jm594slifuumdo6jht6xt8104wi87.gif

很明显,这个问题大概率出现 网络不稳定的情况下当然也可能是来自 sql 语句写的问题

如何解决

有两种:

  1. 说服 甲方 or 产品 ,通过增加 loading 状态来解决,参考 ant-proTable 的解决办法
  2. 终止(取消)上次请求,只响应获取最新的请求返回值 方案二是一种比较通用且没那么费劲的解决办法

在 umijs 中该怎么解决

如果你使用的是 umijs@4 的请求,那么本文的方法还有用,毕竟底层还是依赖 axios 实现的,参考 axios 官网,已经支持 AbortController 取消请求。 upload_nr60bd3m4vmle0qdwafjxevebdgut4f0.png 因为 只保留最新的请求,那么只要把历史的 controller 取消了即可,可以用 闭包 解决,但既然是 ReactuseRef 保存的对象,不就是天然的闭包对象吗,将原来的请求再 包裹 下,核心逻辑代码如下:

const umiCancel = useRef<AbortController | null>(null);

const newRequest = () => {
    if (umiCancel.current) {
      umiCancel.current.abort();
    }
    const controller = new AbortController();
    umiCancel.current = controller;
    return request('/api/xx', { signal: controller.signal })
}

但,如果你的项目启用了类似 plugin-openapi 这种生成 service 能力的功能, 推荐你使用 @siroi/use-once-request这个插件

使用方法

pnpm add @siroi/use-once-request

它有两个 hook

  1. useRaceRequest :适用于修改单一请求,即只包装一个请求
  2. useRaceRequests : 适用于批量修改请求,传入一个请求数组,对每一个请求都进行包装返回 想法逻辑:因为自动生成的 service 代码一定包含 axios 的配置项,那么便可以通过 service 代码的参数和传入的参数个数来更新 axios 的配置项能力。

eg. 生成的接口

export async function createDept(body: API.DeptCreateReqVO, options?: { [key: string]: any }) {
  return request<API.CommonResultLong>('/api/v1/system/demo/create', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}

业务使用

import useRaceRequest, {useRaceRequests} from "@siroi/use-once-request"

const currentRequest = useRaceRequest(createDept);
//or
const [currentRequest] = useRaceRequesrs([createDept]);

让我们启个 express 模拟一下慢接口: upload_9y2cttg0i3fptkvlhpvybo7sp475grhi.png

最终效果 upload_7a8gcze0rfvg9ku5owq3iefwljnzb8es.gif

参考资料

  1. juejin.cn/post/697071…
  2. juejin.cn/post/719473…