上篇 —— async-validator 源码解析(四):Schema 类 —— 将 async-validator
校验库的 Schema类
代码进行了分析。但是由于核心方法 validate
代码量过长,上次剔除了出来。本篇继续分析 async-validator
校验库的Schema类
中的核心方法 validate
。
由于 validate
的代码整体较长,而且大量的用到了闭包和回调陷阱,各种反复横跳、console
加 debugger
后,又参考了一个大佬对老版本代码的分析 ,终于看懂了主要的执行的逻辑。本篇按数据流向详解 validate
方法的全部代码,然后给出用到的 util
中的工具函数的分析。可以从仓库 https://github.com/MageeLin/async-validator-source-code-analysis 的 analysis
分支看到本篇中的代码分析。
已完成:
async-validator 源码解析(一):文档翻译
async-validator 源码解析(二):rule
async-validator 源码解析(三):validator
async-validator 源码解析(四):Schema 类
async-validator 源码解析(五):校验方法 validate
validate validate
方法的前半部分主要是在构造一个完整的series
对象,后半部分是一个 asyncMap
方法,本身 asyncMap
就是个挺复杂的方法,但是它又接收两个回调函数作为参数,回调函数也及其复杂,具有多层回调并且平级之间互相调用。其实作者把这块写的这么复杂的原因是为了闭包和异步。就是为了能间接操作闭包中的 errors
数组,通过每一次迭代校验将其完善成一个最终的 error
结果,再调用闭包中的 callback
将结果返回。
下面用一个数据流模型图看一下 validate
方法的工作原理:
将整个过程分成三部分
生成 series
对象。黄色 💛
迭代 series
对象,连续的执行单次校验。绿色 💚
处理最终的错误对象,通过 callback
回调调用或者 promise
返回。蓝色 💙
生成 series 对象 处理参数 此处是为了把第二个参数 options
变为可选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 validate(source_, o = {}, oc = () => {}) { let source = source_; let options = o; let callback = oc; if (typeof options === 'function' ) { callback = options; options = {}; } }
定义 complete 函数 定义这个函数是为了完善 errors数组
为 fields对象
,然后用 callback
把他们都返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 function complete (results ) { let i; let errors = []; let fields = {}; function add (e ) { if (Array .isArray(e)) { errors = errors.concat(...e); } else { errors.push(e); } } for (i = 0 ; i < results.length; i++) { add(results[i]); } if (!errors.length) { errors = null ; fields = null ; } else { fields = convertFieldsError(errors); } callback(errors, fields); }
options.messages 处理 messages
,根据情况使用默认 messages
或合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (options.messages) { let messages = this .messages(); if (messages === defaultMessages) { messages = newMessages(); } deepMerge(messages, options.messages); options.messages = messages; } else { options.messages = this .messages(); }
生成 series 对象 在这一步生成了本层深度的最终 series
,统一了数据格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 let arr;let value;const series = {};const keys = options.keys || Object .keys(this .rules);keys.forEach((z ) => { arr = this .rules[z]; value = source[z]; arr.forEach((r ) => { let rule = r; if (typeof rule.transform === 'function' ) { if (source === source_) { source = { ...source }; } value = source[z] = rule.transform(value); } if (typeof rule === 'function' ) { rule = { validator: rule, }; } else { rule = { ...rule }; } rule.validator = this .getValidationMethod(rule); rule.field = z; rule.fullField = rule.fullField || z; rule.type = this .getType(rule); if (!rule.validator) { return ; } series[z] = series[z] || []; series[z].push({ rule, value, source, field: z, }); }); });
迭代 series 对象 迭代 series
时用的是 asyncMap(objArr, option, func, callback)
。为了便于理解,按照实际效果,把参数是命名为asyncMap(series, option, singleValidator, completeCallback)
;
singleValidator singleValidator(data, doIt)
是在是太长了,所以把他单独拿出来分析。它的参数 data
是 series
下属数组中的每一个元素,doIt
参数是用于执行下一个 rule
的校验或者最终回调。实现了 singleValidator
和 doIt
的来回互相调用,也就实现了迭代的效果。
内部定义的 cb
函数是 singleValidator
和 doIt 之间互相调用的间接桥梁,用来处理不同条件的特殊情况和深层校验的启动,也是实现异步校验的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 (data, doIt) => { const rule = data.rule; let deep = (rule.type === 'object' || rule.type === 'array' ) && (typeof rule.fields === 'object' || typeof rule.defaultField === 'object' ); deep = deep && (rule.required || (!rule.required && data.value)); rule.field = data.field; function addFullfield (key, schema ) { return { ...schema, fullField: `${rule.fullField} .${key} ` , }; } function cb (e = [] ) { let errors = e; if (!Array .isArray(errors)) { errors = [errors]; } if (!options.suppressWarning && errors.length) { Schema.warning('async-validator:' , errors); } if (errors.length && rule.message) { errors = [].concat(rule.message); } errors = errors.map(complementError(rule)); if (options.first && errors.length) { errorFields[rule.field] = 1 ; return doIt(errors); } if (!deep) { doIt(errors); } else { if (rule.required && !data.value) { if (rule.message) { errors = [].concat(rule.message).map(complementError(rule)); } else if (options.error) { errors = [ options.error(rule, format(options.messages.required, rule.field)), ]; } return doIt(errors); } let fieldsSchema = {}; if (rule.defaultField) { for (const k in data.value) { if (data.value.hasOwnProperty(k)) { fieldsSchema[k] = rule.defaultField; } } } fieldsSchema = { ...fieldsSchema, ...data.rule.fields, }; for (const f in fieldsSchema) { if (fieldsSchema.hasOwnProperty(f)) { const fieldSchema = Array .isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]]; fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null , f)); } } const schema = new Schema(fieldsSchema); schema.messages(options.messages); if (data.rule.options) { data.rule.options.messages = options.messages; data.rule.options.error = options.error; } schema.validate(data.value, data.rule.options || options, (errs) => { const finalErrors = []; if (errors && errors.length) { finalErrors.push(...errors); } if (errs && errs.length) { finalErrors.push(...errs); } doIt(finalErrors.length ? finalErrors : null ); }); } } let res; if (rule.asyncValidator) { res = rule.asyncValidator(rule, data.value, cb, data.source, options); } else if (rule.validator) { res = rule.validator(rule, data.value, cb, data.source, options); if (res === true ) { cb(); } else if (res === false ) { cb(rule.message || `${rule.field} fails` ); } else if (res instanceof Array ) { cb(res); } else if (res instanceof Error ) { cb(res.message); } } if (res && res.then) { res.then( () => cb(), (e) => cb(e) ); } };
completeCallback 这个是方法是去调用前文解析过的闭包中 complete
函数,也就是最终的处理过程。
1 2 3 (results) => { complete(results); };
asyncMap 异步迭代用的 asyncMap
函数并没有多长,它主要实现两个功能,第一是决定是串行还是并行的执行单步校验,第二个功能是实现异步,把整个迭代校验过程封装到一个 promise
中,实现了整体上的异步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 export function asyncMap (objArr, option, func, callback ) { if (option.first) { const pending = new Promise ((resolve, reject ) => { const next = (errors ) => { callback(errors); return errors.length ? reject(new AsyncValidationError(errors, convertFieldsError(errors))) : resolve(); }; const flattenArr = flattenObjArr(objArr); asyncSerialArray(flattenArr, func, next); }); pending.catch((e ) => e); return pending; } let firstFields = option.firstFields || []; if (firstFields === true ) { firstFields = Object .keys(objArr); } const objArrKeys = Object .keys(objArr); const objArrLength = objArrKeys.length; let total = 0 ; const results = []; const pending = new Promise ((resolve, reject ) => { const next = (errors ) => { results.push.apply(results, errors); total++; if (total === objArrLength) { callback(results); return results.length ? reject( new AsyncValidationError(results, convertFieldsError(results)) ) : resolve(); } }; if (!objArrKeys.length) { callback(results); resolve(); } objArrKeys.forEach((key ) => { const arr = objArr[key]; if (firstFields.indexOf(key) !== -1 ) { asyncSerialArray(arr, func, next); } else { asyncParallelArray(arr, func, next); } }); }); pending.catch((e ) => e); return pending; }
asyncParallelArray 异步并行校验时,遇到校验失败的情况时并不会中断执行,继续向下校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 function asyncParallelArray (arr, func, callback ) { const results = []; let total = 0 ; const arrLength = arr.length; function count (errors ) { results.push.apply(results, errors); total++; if (total === arrLength) { callback(results); } } arr.forEach((a ) => { func(a, count); }); }
asyncSerialArray 异步串行校验时,遇到校验失败的情况时会立即中断执行,将当前校验结果直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function asyncSerialArray (arr, func, callback ) { let index = 0 ; const arrLength = arr.length; function next (errors ) { if (errors && errors.length) { callback(errors); return ; } const original = index; index = index + 1 ; if (original < arrLength) { func(arr[original], next); } else { callback([]); } } next([]); }
处理错误对象并返回 既能用回调函数也能用 promise
返回结果的原因就是在这里,到了最终返回结果的阶段,用两种方式来返回。
1 2 3 4 callback(results); return results.length ? reject(new AsyncValidationError(results, convertFieldsError(results))) : resolve();
关键点 在 validate
校验的实现中,有几个关键的地方需要特别说明
深度校验 通过 rule
来判断是否需要深度校验。需要深度校验时将深层的 rule
来 new
一个新的 Schema
对象来进行校验。
1 2 3 4 5 6 7 8 9 10 11 12 let deep = (rule.type === 'object' || rule.type === 'array' ) && (typeof rule.fields === 'object' || typeof rule.defaultField === 'object' ); deep = deep && (rule.required || (!rule.required && data.value)); if (deep) { const schema = new Schema(fieldsSchema); schema.validate(...) }
异步校验 异步校验的关键就是 asyncMap(开始) -> asyncParallelArray(asyncSerialArray) -> doIt -> cb(在这里实现异步) -> asyncParallelArray(asyncSerialArray)-> ... -> completeCallback(返回最终结果)
,循环调用实现的每一次校验。
统一错误处理 外层创建了一个 result
数组,内层定义了一个 next
函数,不管在哪里调用 next
,都会向闭包中的 result
数组中统一添加 error
,便于最终改为errors和field
的返回。
1 2 3 4 5 6 7 8 9 const results = [];const pending = new Promise ((resolve, reject ) => { const next = (errors ) => { results.push.apply(results, errors); }; });