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