接口重复请求

45 阅读2分钟

css

.btn {
  pointer-events: all;
  animation: btnAnim 4s step-end forwards;
}
.btn:active {
  animation: none
}
@keyframes btnAnim {
  from {
      pointer-events: none;
  }
  to {
      pointer-events: all;
  }
}

优点:比较简洁

弊端:没法精准控制接口返回时间,可能有误差,只针对有点击事件的

useLockFn

function useLockFnSelf<P extends any[] = any[], V = 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);
        lockRef.current = false;
        return ret;
      } catch (e) {
        lockRef.current = false;
        throw e;
      }
    },
    [fn],
  );
}

useLockFn 接收一个函数 fn,它接收一个异步函数 fn 作为参数,并返回一个带有锁定状态的函数。这个函数在执行异步操作时,会确保在上一个操作执行完成之前不会被重复调用。这样可以避免出现并发执行的问题。

umi-request全局处理


const set = new Set();
requestInterceptors: [
        (url, options) => {
          // 在发送请求之前做一些处理
          const { method, data, ...restOptions } = options;
          //请求方法+链接+参数的字符串
          const requestId = `${method}-${url}-${JSON.stringify(data)||''}`;

          if (set.has(key)) {
            // 如果请求已经存在,则直接返回一个 reject 状态的 Promise,能拦截掉请求
            return Promise.reject();
          }

          requestMap[requestId] = true;

          return {
            url,
            options: {
              method,
              data,
              ...restOptions,
            },
          };
        },
      ],
      responseInterceptors: [
        (response, options) => {
          // 在接收到响应后做一些处理
          const { method, url, data } = options;
          const requestId = `${method.toLowerCase()}-${url}-${JSON.stringify(data)||''}`;
          set.delete(key)
          return response;
        },
      ],
  1. request的options,method是大写和response 中的method拿到的是小写

  2. 检测到重复的,写Promise.resolve(),还是会发起请求

  3. JSON.stringify(data)可能有坑,建议用更稳妥的组件库

function Test() { 
   const handleClick = async () => {     
        const a = await fnA();       
        const b = await fnB();  
   } 
  return <button onClick={handleClick}>接口请求</button>
}

预期的是对整个handleClick做防重,在fnA, fnB 接口都完成前,都不再发送请求。但请求库只能支持对fnA和fnB做单独的防重 useLockFn就能做到

封一个button组件

import React, { useState } from 'react';
import { Button } from 'antd';
export default function LockButton(props) { 
 const [loading, setLoading] = useState(false); 
 const onClick = async (e) => {   
  if (typeof props.onClick === 'function') {     
   setLoading(true);      
   try {        
       await props.onClick(e);    
     } 
    finally {       
      setLoading(false);    
    }    
   }  
  }; 
   return (    
   <Button      
       {...props}      
       loading={loading || props.loading}      
       disabled={loading || props.disabled}      
       onClick={onClick}   
       />  
    );
    }

babel插件

babel-plugin-async-lock

// 源代码
const onClick = async () => {
  console.log('async function')
  await fnA();
}

// 经babel插件处理后的代码
function _lock$(fn) {
  let locking = false;

  const _lockedFn$ = async function (...args) {
    if (locking) {
      return;
    }

    try {
      locking = true;
      const r = await fn.apply(this, args);
      return r;
    } catch (e) {
      throw e;
    } finally {
      locking = false;
    }
  };

  return _lockedFn$;
}

const onClick = _lock$(async () => {
  console.log('async function')
  await fnA();
});