async-validator 原始碼學習筆記(六):validate 方法

前端人 發表於 2022-03-30

系列文章:

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 方法校驗的流程為:

async-validator 原始碼學習筆記(六):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;
}