Async Validator 使用技巧与常见问题解决方案
Async Validator踩坑实录:异步校验的那些糟心事
最近在做一个表单校验的需求,涉及到用户名的异步唯一性校验。说起来简单,就是用户输入完用户名后,去后端查一下这个用户名有没有被注册过。结果这玩意儿折腾了我大半天,这里把踩坑过程记录一下。
先说问题:用Ant Design的Form组件配合async-validator做校验时,发现异步校验总是不生效。具体表现是:第一次输入用户名时校验正常,但修改后再输入就没反应了。这里我踩了个坑,以为是组件的问题,换了好几个库都没解决。
排查过程:从怀疑到真相
最开始我以为是Ant Design的问题,毕竟它封装了一层校验逻辑。试了下直接用原生的async-validator,发现问题依然存在。又怀疑是不是Promise写法有问题,改成async/await也不行。
折腾了半天发现,问题出在校验规则的返回值上。我最初的写法是这样的:
const validateUsername = (_, value) => {
return new Promise((resolve, reject) => {
fetch(https://jztheme.com/api/check-username?username=${value})
.then(res => res.json())
.then(data => {
if (data.exists) {
reject(new Error('用户名已存在'));
} else {
resolve();
}
});
});
};
看着好像没问题,但实际上有个大坑:当用户快速输入时,多个请求会同时发出,导致后面的请求覆盖前面的结果。这就造成了有时候校验结果显示异常的情况。
核心代码就这几行:最终解决方案
后来试了下发现,得加个取消机制。这里是改进后的完整代码:
let currentRequest = null;
const validateUsername = (_, value) => {
// 取消之前的请求
if (currentRequest) {
currentRequest.abort();
}
return new Promise((resolve, reject) => {
currentRequest = fetch(https://jztheme.com/api/check-username?username=${value}, {
signal: new AbortController().signal
});
currentRequest
.then(res => res.json())
.then(data => {
if (data.exists) {
reject(new Error('用户名已存在'));
} else {
resolve();
}
})
.catch(err => {
if (err.name !== 'AbortError') {
console.error('校验出错', err);
reject(new Error('校验失败,请稍后重试'));
}
});
});
};
这里有几个关键点要注意:
- AbortController:用来取消未完成的请求,防止请求堆积
- 错误处理:要区分是用户主动取消还是其他错误
- 防抖处理:虽然加了取消机制,但最好还是加个防抖,减少不必要的请求
一些延伸思考:为什么这么设计
其实这个方案还不是最完美的。比如说,如果用户网络特别慢,可能第一个请求还没返回就被第二个请求取消了。不过在实际项目中,这种情况出现的概率不大,暂时可以接受。
这里再补充一个细节:很多人喜欢在校验函数里直接return Promise,但这种方式在复杂场景下很容易出问题。建议统一用resolve和reject来处理,逻辑会更清晰。
顺便提一下性能优化的小技巧:可以在本地维护一个Set,缓存已经校验过的用户名。这样同一个用户名多次输入时就不需要重复请求了:
const usernameCache = new Set();
const validateUsername = (_, value) => {
if (usernameCache.has(value)) {
return Promise.resolve();
}
// 其余逻辑不变...
};
踩坑提醒:这三点一定注意
总结一下容易踩坑的地方:
- 不要忽略请求取消机制,否则会出现”幽灵请求”
- 错误处理要细致,特别是网络异常情况
- 记得加防抖和缓存,提升用户体验的同时也能减轻服务器压力
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。这个需求虽然看起来简单,但要做好还是挺考验基本功的。前端开发就是这样,越基础的东西越容易出幺蛾子。

暂无评论