async-validator 源码学习笔记(六):validate 方法
系列文章:
1、async-validator 源码学习(一):文档翻译
2、async-validator 源码学习笔记(二):目录结构
3、async-validator 源码学习笔记(三):rule
4、async-validator 源码学习笔记(四):validator
5、async-validator 源码学习笔记(五):Schema
一、validate 介绍
validate 是 async-validator 的核心方法,不仅需要掌握它的使用,也需要了解它的原理。
使用
validator.validate( source, [options], callback ) .then(()=>{}) .catch( ({errors, fields}) => {})
参数
- source 是需要验证的对象
- options 是描述验证的处理选项的对象
- callback 校验完成的回调函数
返回值是一个 promise 对象
- then 是校验通过执行。
- catch 校验失败执行。
- errors 是 error 的数组,fields 是一个对象,包含监听对象和 error 的数组。
validate 方法校验的流程为:
源码是如何定义 validate 方法的呢?
二、validate 源码解读
/* 参数: source_ 即 source :校验的对象。 o 即 options :描述验证处理选项。 oc 即 callback:验证完成的回调函数。 */ _proto.validate = function validate(source_, o, oc) { ... var source = source_; var options = o; var callback = oc; ... return asyncMap(series, options, function (data, doIt) { .... },function (results) { complete(results); }, source); };
validate 方法前半部分主要是在构造一个完整的 series 对象,返回的是 asyncMap 方法。我们来看看 validate 方法内部的几个方法,分别作用是什么。
参数处理
_proto.validate = function validate(source_, o, oc) { ... var source = source_; var options = o; var callback = oc; // options 是可选参数 // 如果 options 是函数时,说明第二个是回调函数,options 是空对象 if (typeof options === 'function') { callback = options; options = {}; } ... };
检查校验规则
检查校验规则是否为空,为空的时候立即执行回调。
if (!this.rules || Object.keys(this.rules).length === 0) { if (callback) { callback(null, source); } return Promise.resolve(source); }
complete 函数
complete 函数主要是为了整合 errors 数组和 fields 对象,然后用 callback 回调函数把它们返回。
_proto.validate = function validate(source_, o, oc) { ... function complete(results) { var errors = []; var fields = {}; // 定义 add 方法,给 errors 添加元素 error function add(e) { if (Array.isArray(e)) { var _errors; // 给 errors 添加 error errors = (_errors = errors).concat.apply(_errors, e); } else { errors.push(e); } } // 迭代 resaults 把 resaults 中的每个 error 都加入 errors for (var i = 0; i < results.length; i++) { add(results[i]); } //如果最后结果 errors 为空,就返回 null if (!errors.length) { callback(null, source); } else { //把 errors 中相同 field 的 error 合并,转换为对象的形式 fields = convertFieldsError(errors); // errors fields 回调传出参数 callback(errors, fields); } } return asyncMap(series, options, function (data, doIt) { .... },function (results) { complete(results); }, source); };
options.message
messsage 主要是定义检验失败后的错误提示信息,官方提供了一个默认模板,我们也可以进行定制化,此处的 options.message 就是来处理到底使用哪个的?根据情况到底是使用默认还是合并。
_proto.validate = function validate(source_, o, oc) { ... //如果 options 中有 message 属性 if (options.messages) { // 创建一个 message ,使用的是默认 var messages$1 = this.messages(); if (messages$1 === messages) { messages$1 = newMessages(); } // 将options 的 message 与默认的 message 合并 deepMerge(messages$1, options.messages); options.messages = messages$1; } else { // options 没有 message 属性 options.messages = this.messages(); } return asyncMap(series, options, function (data, doIt) { .... },function (results) { complete(results); }, source); };
series 对象
生成的 serise 对象,目的是为了统一最终的数据格式。
_proto.validate = function validate(source_, o, oc) { ... var series = {}; // keys 是 rule 的所有键 var keys = options.keys || Object.keys(this.rules); keys.forEach(function (z) { // arr 存放 rules[z] 的一个数组 var arr = _this2.rules[z]; // value 存放 source[z] 是一个值或对象 var value = source[z]; arr.forEach(function (r) { var rule = r; // 当有transform属性而且是个函数时,要提前把值转换 if (typeof rule.transform === 'function') { // 浅拷贝下,打破引用 if (source === source_) { source = _extends({}, source); } value = source[z] = rule.transform(value); } // 浅拷贝打破引用 if (typeof rule === 'function') { rule = { validator: rule }; } else { rule = _extends({}, rule); } // Fill validator. Skip if nothing need to validate rule.validator = _this2.getValidationMethod(rule); // 异常处理 if (!rule.validator) { return; } rule.field = z; rule.fullField = rule.fullField || z; rule.type = _this2.getType(rule); // 生成完整的 series series[z] = series[z] || []; series[z].push({ rule: rule, value: value, source: source, field: z }); }); }); return asyncMap(series, options, function (data, doIt) { .... },function (results) { complete(results); }, source); };
asyncMap
asyncMap 作为一个返回函数,不得不说它又是什么内容呢?
异步迭代用的 asyncMap 函数并没有多长,它主要实现两个功能,第一是决定是串行还是并行的执行单步校验,第二个功能是实现异步,把整个迭代校验过程封装到一个 promise 中,实现了整体上的异步。
function asyncMap(objArr, option, func, callback, source) { // 如果option.first选项为真,说明第一个error产生时就要报错 if (option.first) { // pending 是一个promise var _pending = new Promise(function (resolve, reject) { // 定义一个函数next,这个函数先调用callback,参数是errors // 再根据errors的长度决定resolve还是reject var next = function next(errors) { callback(errors); // reject的时候,返回一个AsyncValidationError的实例 // 实例化时第一个参数是errors数组,第二个参数是对象类型的errors return errors.length ? reject(new AsyncValidationError(errors, convertFieldsError(errors))) : resolve(source); }; // 把对象扁平化为数组flattenArr var flattenArr = flattenObjArr(objArr); // 串行 asyncSerialArray(flattenArr, func, next); }); // 捕获error _pending["catch"](function (e) { return e; }); return _pending; } // 如果option.first选项为假,说明所有的error都产生时才报错 // 当指定字段的第一个校验规则产生error时调用callback,不再继续处理相同字段的校验规则。 var firstFields = option.firstFields === true ? Object.keys(objArr) : option.firstFields || []; var objArrKeys = Object.keys(objArr); var objArrLength = objArrKeys.length; var total = 0; var results = []; // 这里定义的函数next和上面的类似,只不过多了total的判断 var pending = new Promise(function (resolve, reject) { var next = function next(errors) { results.push.apply(results, errors); // 只有全部的校验完才能执行最后的callback和reject total++; if (total === objArrLength) { // 这个callback和reject/resolve是这个库既能回调函数又能promise的核心 callback(results); return results.length ? reject(new AsyncValidationError(results, convertFieldsError(results))) : resolve(source); } }; if (!objArrKeys.length) { callback(results); resolve(source); } // 当firstFields中指定了该key时,说明该字段的第一个校验失败产生时就停止并调用callback // 所以是串行的asyncSerialArray // 没有指定该key,说明该字段的校验error需要都产生,就并行asyncParallelArray objArrKeys.forEach(function (key) { var arr = objArr[key]; if (firstFields.indexOf(key) !== -1) { asyncSerialArray(arr, func, next); } else { asyncParallelArray(arr, func, next); } }); }); // 捕获error,添加错误处理 pending["catch"](function (e) { return e; }); // 返回promise实例 return pending; }