淘汰赛验证异步验证器:这是一个错误还是我做错了啥?

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__ 设置为falseko.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 转换为错误的日期 - 是错误还是我做错了啥?

JSON.parse,我做错了啥?

OAuthException error_message:“无效范围:[]”我做错了啥?

Sed 不匹配反斜杠文字还是我做错了啥?

创建这个动态生成的表时我做错了啥?

波形写入功能不起作用,我做错了啥?