avatarGithub

用babel插件实现按钮防重

2022-04-04

业务中总是会出现按钮多次点击,导致接口被重复请求的情况。而目前的解决方案,可归纳为以下两种

从组件入手

输出一个通用的组件,处理该问题


import React, { useState } from 'react';
import { Button } from 'antd';
export default function PromiseButton(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}
/>
);
}

在使用到Button的地方,统一用PromiseButton组件替代即可

这个方案的问题在于

  1. 场景比较局限,实际业务中并不是只有按钮点击才会触发接口请求
  2. 比较难统一,如何保证其他人,其他团队会用这个组件?也许可以用eslint做提示,但比较难推广

从请求库入手

一般团队里都会有统一的请求库,可以从请求库入手。举个最简单的例子


const set = new Set();
async function post(url, data) {
const key = `${url}_${JSON.stringify(data)}`;
if (set.has(key)) {
return;
}
set.add(key)
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
}
set.delete(key)
return res;
}

以请求路径和请求参数作为一个接口请求的唯一标识,利用Set做接口请求状态的管理

这个方案的问题在于无法对多个请求做防重


import React from 'react';
import post from './post';
function Test() {
const handleClick = async () => {
const a = await post('/a', {});
const b = await post('/b', {});
}
return <button onClick={handleClick}>接口请求</button>
}

比如以上这种情况,预期的是对整个handleClick做防重,在/a, /b 接口都完成前,都不再发送请求。但请求库只能支持对/a/b做单独的防重

babel插件方案

在一次思考babel插件处理async函数的过程中,我突然想到,能不能用类似的思路,给代码里的每一个async方法,都加一个防重处理呢?


// 源代码
const onClick = async () => {
console.log('async function')
await post('/a', {});
}
// 经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 post('/a', {});
});

这个方案很好的解决了原来两个方案的问题

  1. 对开发者无感,业务开发不需要想着每个用Button的地方都要用PromiseButton替代
  2. 覆盖到所有使用async函数的地方
  3. 支持对多个请求做防重

插件源代码奉上babel-plugin-async-lock