本文是深入浅出 ahooks 源码系列文章的第四篇,该系列已整顿成文档-地址。感觉还不错,给个 关注 反对一下哈,Thanks。

本文来摸索一下 ahooks 的 useLockFn。并由此探讨一个很常见的场景,勾销反复申请。

场景

试想一下,有这么一个场景,有一个表单,你可能屡次提交,就很可能导致后果不正确。

解决这类问题的办法有很多,比方增加 loading,在第一次点击之后就无奈再次点击。另外一种办法就是给申请异步函数增加上一个动态锁,避免并发产生。这就是 ahooks 的 useLockFn 做的事件。

useLockFn

useLockFn 用于给一个异步函数减少竞态锁,避免并发执行。

它的源码比较简单,如下所示:

import { useRef, useCallback } from 'react';// 用于给一个异步函数减少竞态锁,避免并发执行。function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {  // 是否当初处于一个锁中  const lockRef = useRef(false);  // 返回的是减少了竞态锁的函数  return useCallback(    async (...args: P) => {      // 判断申请是否正在进行      if (lockRef.current) return;      // 申请中      lockRef.current = true;      try {        // 执行原有申请        const ret = await fn(...args);        // 申请实现,状态锁设置为 false        lockRef.current = false;        return ret;      } catch (e) {        // 申请失败,状态锁设置为 false        lockRef.current = false;        throw e;      }    },    [fn],  );}export default useLockFn;

能够看到,它的入参是异步函数,返回的是一个减少了竞态锁的函数。通过 lockRef 做一个标识位,初始化的时候它的值为 false。当正在申请,则设置为 true,从而下次再调用这个函数的时候,就间接 return,不执行原函数,从而达到加锁的目标。

毛病

尽管实用,但毛病很显著,我须要给每一个须要增加竞态锁的申请异步函数都手动加一遍。那有没有比拟通用和不便的办法呢?

答案是能够通过 axios 主动勾销反复申请。

axios 主动勾销反复申请

axios 勾销申请

对于原生的 XMLHttpRequest 对象发动的 HTTP 申请,能够调用 XMLHttpRequest 对象的 abort 办法。

那么咱们我的项目中罕用的 axios 呢?它其实底层也是用的 XMLHttpRequest 对象,它对外裸露勾销申请的 API 是 CancelToken。能够应用如下:

const CancelToken = axios.CancelToken;const source = CancelToken.source();axios.post('/user/12345', {  name: 'gopal'}, {  cancelToken: source.token})source.cancel('Operation canceled by the user.'); // 勾销申请,参数是可选的

另外一种应用的办法是调用 CancelToken 的构造函数来创立 CancelToken,具体应用如下:

const CancelToken = axios.CancelToken;let cancel;axios.get('/user/12345', {  cancelToken: new CancelToken(function executor(c) {    cancel = c;  })});cancel(); // 勾销申请

如何主动勾销反复的申请

晓得了如何勾销申请,那怎么做到主动勾销呢?答案是通过 axios 的拦截器。

  • 申请拦截器:该类拦截器的作用是在申请发送前对立执行某些操作,比方在申请头中增加 token 相干的字段。
  • 响应拦截器:该类拦截器的作用是在接管到服务器响应后对立执行某些操作,比方发现响应状态码为 401 时,主动跳转到登录页。

具体的做法如下:

第一步,定义几个重要的辅助函数。

  • generateReqKey:用于依据以后申请的信息,生成申请 Key。只有 key 雷同才会断定为是反复申请。这一点很重要,而且可能跟具体的业务场景无关,比方有一种申请,输入框含糊搜寻,用户高频输出关键字,一次性收回多个申请,可能先收回的申请,最初才响应,导致理论搜寻后果与预期不符。这种其实就只须要依据 URL 和申请办法断定其为反复申请,而后勾销之前的申请就能够了。

这里我认为,如果有需要的话,能够裸露一个 API 给开发者进行自定义反复的规定。这里咱们先依据申请办法、url、以及参数生成惟一的 key 去做。

function generateReqKey(config) {  const { method, url, params, data } = config;  return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");}
  • addPendingRequest。用于把以后申请信息增加到 pendingRequest 对象中。

    const pendingRequest = new Map();function addPendingRequest(config) {const requestKey = generateReqKey(config);config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {  if (!pendingRequest.has(requestKey)) {     pendingRequest.set(requestKey, cancel);  }});}
  • removePendingRequest。查看是否存在反复申请,若存在则勾销已发的申请

    function removePendingRequest(config) {const requestKey = generateReqKey(config);if (pendingRequest.has(requestKey)) {   const cancelToken = pendingRequest.get(requestKey);   cancelToken(requestKey);   pendingRequest.delete(requestKey);}}

第二步,增加申请拦截器。

axios.interceptors.request.use(  function (config) {    removePendingRequest(config); // 查看是否存在反复申请,若存在则勾销已发的申请    addPendingRequest(config); // 把以后申请信息增加到pendingRequest对象中    return config;  },  (error) => {     return Promise.reject(error);  });

第二步,增加响应拦截器。

axios.interceptors.response.use(  (response) => {     removePendingRequest(response.config); // 从pendingRequest对象中移除申请     return response;   },   (error) => {      removePendingRequest(error.config || {}); // 从pendingRequest对象中移除申请      if (axios.isCancel(error)) {        console.log("已勾销的反复申请:" + error.message);      } else {        // 增加异样解决      }      return Promise.reject(error);   });

到这一步,咱们就通过 axios 实现了主动勾销反复申请的性能。

思考与总结

尽管能够通过相似 useLockFn 这样的 hook或办法给申请函数增加竞态锁的形式解决反复申请的问题。但这种还是须要依赖于开发者的习惯,如果没有一些规定的束缚,很难防止问题。

通过 axios 拦截器以及其 CancelToken 性能,咱们可能在拦截器中主动将已发的申请勾销,当然如果有一些接口就是须要反复发送申请,能够思考加一下白名单性能,让申请不进行勾销。