本文是深入浅出 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 性能,咱们可能在拦截器中主动将已发的申请勾销,当然如果有一些接口就是须要反复发送申请,能够思考加一下白名单性能,让申请不进行勾销。