淘汰赛验证异步验证器:这是一个错误还是我做错了啥?
Posted
技术标签:
【中文标题】淘汰赛验证异步验证器:这是一个错误还是我做错了啥?【英文标题】:Knockout Validation async validators: Is this a bug or am I doing something wrong?淘汰赛验证异步验证器:这是一个错误还是我做错了什么? 【发布时间】:2012-09-25 04:12:38 【问题描述】:我非常喜欢 Eric Barnard's knockout validation lib 与 observables 集成、允许分组并提供自定义验证器可插入性(包括即时验证器)的方式。有几个地方可以让用户体验更加灵活/友好,但总体而言,它的文档记录得当...except, imo, when it comes to async validators。
我今天为此苦苦挣扎了几个小时,然后才进行搜索和landing on this。我认为我和原作者有同样的问题/疑问,但同意不清楚 duxa 到底要什么。我想引起这个问题更多的关注,所以我也在这里问。
function MyViewModel()
var self = this;
self.nestedModel1.prop1 = ko.observable().extend(
required: message: 'Model1 Prop1 is required.' ,
maxLength:
params: 140,
message: '0 characters max please.'
);
self.nestedModel2.prop2 = ko.observable().extend(
required: message: 'Model2 Prop2 is required' ,
validation:
async: true,
validator: function(val, opts, callback)
$.ajax( // BREAKPOINT #1
url: '/validate-remote',
type: 'POST',
data: ...some data...
)
.success(function(response)
if (response == true) callback(true); // BREAKPOINT #2
else callback(false);
);
,
message: 'Sorry, server says no :('
);
ko.validation.group(self.nestedModel1);
ko.validation.group(self.nestedModel2);
关于上面代码的几点说明:有 2 个独立的验证组,每个嵌套模型一个。嵌套模型 #1 没有异步验证器,嵌套模型 #2 同时具有同步(必需)和异步。异步调用服务器调用来验证输入。当服务器响应时,callback
参数用于告诉ko.validation
用户输入是好是坏。 如果您在指示的行上放置断点并使用已知的无效值触发验证,您最终会陷入无限循环,其中 ajax success
函数会导致再次调用 validator
函数。 I破解了ko.validation
源代码,看看发生了什么。
ko.validation.validateObservable = function(observable)
// set up variables & check for conditions (omitted for brevity)
// loop over validators attached to the observable
for (; i < len; i++)
if (rule['async'] || ctx['async'])
//run async validation
validateAsync();
else
//run normal sync validation
if (!validateSync(observable, rule, ctx))
return false; //break out of the loop
//finally if we got this far, make the observable valid again!
observable.error = null;
observable.__valid__(true);
return true;
这个函数在一个订阅链中,附加到用户输入的 observable 中,所以当它的值改变时,新的值将被验证。该算法在每个附加到输入的验证器上循环,并根据验证器是否异步执行单独的函数。如果同步验证失败,则循环中断并且整个validateObservable
函数退出。如果所有同步验证器都通过,则执行最后 3 行,本质上是告诉 ko.validation
此输入有效。库中的__valid__
函数如下所示:
//the true holder of whether the observable is valid or not
observable.__valid__ = ko.observable(true);
有两点需要注意:__valid__
是一个 observable,在validateAsync
函数退出后它被设置为true
。现在我们来看看validateAsync
:
function validateAsync(observable, rule, ctx)
observable.isValidating(true);
var callBack = function (valObj)
var isValid = false,
msg = '';
if (!observable.__valid__())
// omitted for brevity, __valid__ is true in this scneario
//we were handed back a complex object
if (valObj['message'])
isValid = valObj.isValid;
msg = valObj.message;
else
isValid = valObj;
if (!isValid)
//not valid, so format the error message...
observable.error = ko.validation.formatMessage(...);
observable.__valid__(isValid);
// tell it that we're done
observable.isValidating(false);
;
//fire the validator and hand it the callback
rule.validator(observable(), ctx.params || true, callBack);
需要注意的是,在ko.validation.validateObservable
将__valid__
observable 设置为true 并退出之前,只执行了这个函数的第一行和最后一行。 callBack
函数作为第三个参数传递给MyViewModel
中声明的异步validator
函数。然而在此之前,isValidating
observable 的订阅者被调用以通知异步验证已经开始。当服务器调用完成时,回调被调用(在这种情况下只是传递 true 或 false)。
现在,当服务器端验证失败时,MyViewModel
中的断点会导致无限乒乓循环:在上面的 callBack
函数中,请注意验证失败时 __valid__
observable 是如何设置为 false 的。以下是发生的事情:
-
无效的用户输入改变了
nestedModel2.prop2
observable。
ko.validation.validateObservable
会通过订阅收到此更改的通知。
validateAsync
函数被调用。
调用自定义异步验证器,向服务器提交异步$.ajax
调用并退出。
ko.validation.validateObservable
将__valid__
observable 设置为true
并退出。
服务器返回无效响应,并执行callBack(false)
。
callBack
函数将__valid__
设置为false
。
ko.validation.validateObservable
被通知 __valid__
可观察对象的更改(callBack
将其从 true
更改为 false
)这基本上重复了上面的步骤 2。
重复上述步骤 3、4 和 5。
由于 observable 的值没有改变,服务器返回另一个无效响应,触发上面的步骤 6、7、8 和 9。
我们自己有一场乒乓球比赛。
所以问题似乎在于ko.validation.validateObservable
订阅处理程序不仅在监听用户输入值的变化,而且还在监听其嵌套的__valid__
observable 的变化。这是一个错误,还是我做错了什么?
次要问题
您可以从上面的ko.validation
来源中看到,带有异步验证器的用户输入值在服务器对其进行验证时被视为有效。正因为如此,打电话nestedModel2.isValid()
不能依赖“真相”。相反,看起来我们必须使用isValidating
挂钩来创建对异步验证器的订阅,并且只有在它们通知false
的值后才能做出这些决定。这是设计使然吗?与库的其他部分相比,这似乎是最反直觉的,因为 non 异步验证器没有要订阅的 isValidating
,并且 可以 依赖 .isValid()
说实话。这也是设计使然,还是我在这里也做错了什么?
【问题讨论】:
您为这个问题付出了很多努力,很抱歉看到您没有任何回复。我只是 KO 验证的新手,所以我真的帮不了你。 这个问题有一些问题。提出的实际问题尚不清楚,并且隐藏在外部链接中。在意识到这一点之前,我必须阅读几段文字。这个问题有太多的上下文;只见树木不见森林。你似乎在问多个问题。建议:在 JSFiddle 中创建最简单的问题重现,在您的问题中链接到它,并在第一段中提出一个简洁的问题。这样做,您将更有可能得到一些答案。 您能否更新或回答您的问题?我看到你已经从这个网址想出了一个解决方案......github.com/ericmbarnard/Knockout-Validation/issues/145。您的回答将帮助很多人,因为那里没有太多关于异步验证的信息。 【参考方案1】:所以我问的问题确实与如何在 ko.validation 中使用异步验证器有关。我从我的经验中学到了 2 大收获:
不要创建async
Anonymous or Single-Use Custom Rule validators。相反,将它们创建为Custom Rules。否则你会得到我的问题中描述的无限循环/ping ping匹配。
如果您使用 async
验证器,请不要信任 isValid()
,直到所有 async
验证器的 isValidating
subscriptions
更改为 false。
如果您有多个异步验证器,则可以使用如下模式:
var viewModel =
var self = this;
self.prop1 = ko.observable().extend(validateProp1Async: self);
self.prop2 = ko.observable().extend(validateProp2Async: self);
self.propN = ko.observable();
self.isValidating = ko.computed(function()
return self.prop1.isValidating() || self.prop2.isValidating();
);
self.saveData = function(arg1, arg2, argN)
if (self.isValidating())
setTimeout(function()
self.saveData(arg1, arg2, argN);
, 50);
return false;
if (!self.isValid())
self.errors.showAllMessages();
return false;
// data is now trusted to be valid
$.post('/something', 'data', function() doWhatever() );
;
你也可以see this for another reference with similar alternate solutions。
这是一个异步“自定义规则”的示例:
var validateProp1Async =
async: true,
message: 'you suck because your input was wrong fix it or else',
validator: function(val, otherVal, callback)
// val will be the value of the viewmodel's prop1() observable
// otherVal will be the viewmodel itself, since that was passed in
// via the .extend call
// callback is what you need to tell ko.validation about the result
$.ajax(
url: '/path/to/validation/endpoint/on/server',
type: 'POST', // or whatever http method the server endpoint needs
data: prop1: val, otherProp: otherVal.propN() // args to send server
)
.done(function(response, statusText, xhr)
callback(true); // tell ko.validation that this value is valid
)
.fail(function(xhr, statusText, errorThrown)
callback(false); // tell ko.validation that his value is NOT valid
// the above will use the default message. You can pass in a custom
// validation message like so:
// callback( isValid: false, message: xhr.responseText );
);
;
基本上,您使用callback
arg 到validator
函数来告诉ko.validation 验证是否成功。该调用将触发已验证属性 observables 上的 isValidating
observables 更改回 false
(意思是,异步验证已完成,现在知道输入是否有效)。
如果您的服务器端验证端点在验证成功时返回 HTTP 200 (OK) 状态,则上述方法将起作用。这将导致.done
函数执行,因为它等效于$.ajax
success
。如果您的服务器在验证失败时返回 HTTP 400(错误请求)状态,它将触发 .fail
函数执行。如果您的服务器返回带有 400 的自定义验证消息,您可以从 xhr.responseText
获取该消息以有效地覆盖默认的 you suck because your input was wrong fix it or else
消息。
【讨论】:
谢谢你,这对我帮助很大:) 您介意提供一个验证规则示例validateProp1Async
吗?
在淘汰验证 wiki (github.com/ericmbarnard/Knockout-Validation/wiki/Async-Rules) 的异步规则页面上,显示的 ajax 示例让我感到困惑。 validator
不返回真或假。如果它不返回真或假,规则将如何验证该值?这就是我要求查看您的验证规则的原因。抱歉所有问题 - 除了 Eric Barnard 之外,在文档方面并没有太多问题,你是我见过的唯一一个似乎掌握异步工作原理的人。
我更新了答案。验证器函数不返回 true 或 false,这将使其非异步。异步意味着您承诺稍后返回一个值,为此,您需要回调函数。当 ko.validation 调用它时,一个作为验证器函数的第三个参数提供。
ko.validation.registerExtenders();
只有在创建自己的验证器时才需要。该库在内部为开箱即用的验证器调用它。【参考方案2】:
我遇到了同样的问题,嵌套了带有验证的可观察对象。所以一个魔术:
在
self.errors = ko.validation.group(self.submissionAnswers, deep: true, live: true );
注意特殊的附加参数:包含字段live: true
的对象
【讨论】:
以上是关于淘汰赛验证异步验证器:这是一个错误还是我做错了啥?的主要内容,如果未能解决你的问题,请参考以下文章
Gmail API 中的 VacationSettings.endTime 转换为错误的日期 - 是错误还是我做错了啥?