async-validator 源碼學習筆記(六):validate 方法
- 2022 年 3 月 30 日
- 筆記
- async-validator源碼學習
系列文章:
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; }