Validator校验器
一、背景
定义一个Validator校验器,可用于表单验证,但又与表单组件相互独立,参数提供“需要校验的数据”和“校验规则”,返回“校验结果”。(React+Typescript)
例如:(数据格式)
- 需要校验的数据:
//formData:
{username:'lisa', password: '123456',phone:'18888888888'}
- 校验规则:
//rules:
[
{key:'username', require:true, maxLength:20,minLength:2},
{key:'password', require:true, pattern: /^[a-zA-Z0-9_]{8}$/},
{key:'phone', maxLength:11},
]
- 返回结果:
{
username:['必填','太长','...']
password:['太短','...']
}
二、加需求
想在以上功能的基础上实现自定义异步验证,
验证规则中加入一个validate,这里的validate函数通常会出现ajax异步请求:
const rules = [
{key:'username', require:true, maxLength:20,minLength:2},
{key:'password', require:true, pattern: /^[a-zA-Z0-9_]{8}$/},
{key:'phone', maxLength:11,validate: checkPhone},
];
const checkPhone = (value: string, succeed: (reason?: any) => void, fail: (reason?: any) => void) => {
setTimeout(() => {
if (value === 'xxx') {
succeed('电话号码对了');
} else {
fail('电话号码错了');
}
}, 2000);
};
其中,validate(value, resolve, reject)
,参数value
是需要校验的数据,参数resolve
是成功后需要执行的函数,参数reject
是失败后需要执行的函数,reject
的参数就是校验结果的文字内容。
- 思路:
- 只要在Validator校验器中通过Promise执行validate函数,就能执行异函数;
- 然后通过
Promise.then
的回调参数得到异步函数执行后返回的信息内容。(succeed('电话号码对了')
或者fail('电话号码错了')
)
于是有了以下代码:
const Validator = (data, rules) => { let errors = {}; const addError = (k, error) => { if (isEmpty(errors[k])) { errors[k] = []; } errors[k].push(error); }; rules.map((rule) => { if (rule.validate) { new Promise((resolve, reject) => rule.validate(data[rule.key], resolve, reject)) .then(undefined, (fail) => { fail && addError(rule.key, fail); }); } } return errors }
- 但是:
-
Promise.then()
是异步调用,当Validator校验器返回errors时,很有可能Promise.then()里面的reject回调函数还没有执行; - 于是想到了用Promise.all确保validate都已执行并回调了resolve/reject:
const Validator = (data, rules) => { let errors = {}; let p = []; rules.map((rule) => { if (rule.validate) { cosnt p = new Promise((resolve, reject) => rule.validate(data[rule.key], resolve, reject)) p.then(undefined, (fail) => { fail && addError(rule.key, fail); }); pList.push(p); } } Promise.all(pList).then(() => { return errors; }, () => { return errors; }); }
- 发现bug:
- 当执行
Promise.all(p1,p2,p3)
时:
(a): p1、p2、p3状态都是fulfilled时,才会执行then里面的回调函数success,p1、p2、p3的返回值组成一个数组做为success的参数;
(b): p1、p2、p3状态只要有一个是reject,就会执行then里面的回调函数fail,第一个被reject的返回值作为fail的参数。 - 说明在rules里面出现多个异步校验时,如果出现多个错误信息需要返回,很有可能造成信息遗漏。
- 解决方案:
- 通过以上分析,我的思路是只要确保
Promise.all(p1,p2,p3)
里面的p1、p2、p3状态都是fulfilled,就说明validate后面的then被执行了; - 通过查阅发现:
const after = new Promise((resolve, reject) =>{...}).then((ok)=>{}, (fail) =>{});
无论Promise
里面执行的是resolve
还是reject
,返回给after
的状态一定是fulfilled,那么Promise.all([after1, after2])
就会执行then里面的回调函数success:
const Validator = (data, rules) => {
let errors = {};
let promiseList = [];
rules.map((rule) => {
if (rule.validate) {
const after = new Promise((resolve, reject) =>
rule.validate(data[rule.key], resolve, reject))
.then(undefined, (fail) => {
fail && addError(rule.key, fail);
});
promiseList.push(after);
}
}
Promise.all(promiseList).then(() => {
return errors;
});
}
三、源码
import {FormData} from './form';
interface FormRule {
key: string,
required?: boolean,
minLength?: number,
maxLength?: number,
pattern?: RegExp,
validate?: (value: string, succeed: (reason?: any) => void, fail: (reason?: any) => void) => void
}
type FormRules = Array<FormRule>
interface FormErrors {
[K: string]: string[]
}
const isEmpty = (value: any) => {
return value === undefined || value === null || value === '';
};
const Validator = (data: FormData, rules: FormRules, callback: (errors: FormErrors) => void) => {
let errors: any = {};
let promiseList: Promise<void>[] = [];
const addError = (k: string, error: string) => {
if (isEmpty(errors[k])) {
errors[k] = [];
}
errors[k].push(error);
};
rules.map((rule) => {
if (rule.required && isEmpty(data[rule.key])) {
addError(rule.key, '必填');
}
if ((rule.minLength) &&
!isEmpty(data[rule.key]) &&
data[rule.key].length <= rule.minLength) {
addError(rule.key, '太短了');
}
if ((rule.maxLength) &&
!isEmpty(data[rule.key]) &&
data[rule.key].length >= rule.maxLength) {
addError(rule.key, '太长了');
}
if (rule.pattern && !rule.pattern.test(data[rule.key])) {
addError(rule.key, '格式不对');
}
if (rule.validate) {
const after = new Promise<void>(
(resolve, reject) => rule.validate!(data[rule.key], resolve, reject))
.then(undefined, (fail) => {
fail && addError(rule.key, fail);
});
promiseList.push(after);
}
});
Promise.all(promiseList).then(() => {
callback(errors);
});
};
export default Validator;