knockout.js Knockout验证异步验证器:这是一个错误还是我做错了什么?

fd3cxomn  于 2022-11-10  发布在  其他
关注(0)|答案(2)|浏览(155)

我真的很喜欢Eric Barnard's knockout validation lib与可观察性的集成,允许分组,并提供自定义验证器的可插入性(包括动态验证器)。有几个地方它可以更灵活/更友好,但总的来说,它是合理的良好记录... 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);

关于以上代码的几点注意事项:有两个独立的验证组,每个嵌套模型一个。嵌套模型#1没有异步验证器,而嵌套模型#2有两个同步验证器。(必需的)和一个async。async调用服务器调用来验证输入。当服务器响应时,callback参数用于告诉ko.validation用户输入是正确的还是错误的。**如果您在指定的行上放置断点并使用已知的无效值触发验证,您最终会得到一个无限循环,其中 AJAX 1 m2n1x函数导致validator函数被再次调用。**我打开了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;
}

此函数位于附加到可观察用户输入的订阅链中,因此当其值更改时,将验证新值。该算法在附加到输入的每个验证器上循环,并根据验证器是否为异步执行单独的函数。如果同步验证失败,则循环中断,整个validateObservable函数退出。如果所有同步验证器都通过,最后3行被执行,实质上告诉ko.validation这个输入是有效的。库中的__valid__函数如下所示:

//the true holder of whether the observable is valid or not
observable.__valid__ = ko.observable(true);

有两点值得注意:__valid__是一个可观测量,在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__可观测值设置为true并退出之前,只执行了该函数的第一行和最后一行。callBack函数是作为第三个参数传递给MyViewModel中声明的异步validator函数的函数。然而,在此之前,一个isValidating可观测对象的订阅者被调用来通知异步验证已经开始。当服务器调用完成时,回调被调用(在这种情况下,只传递true或false)。
下面是当服务器端验证失败时,MyViewModel中的断点导致无限乒乓循环的原因:在上面的callBack函数中,请注意当验证失败时,__valid__可观测值是如何被设置为false的。
1.无效的用户输入更改了nestedModel2.prop2可观察值。

  1. ko.validation.validateObservable会透过订阅通知此变更。
    1.调用validateAsync函数。
    1.调用自定义异步验证器,该验证器向服务器提交异步$.ajax调用并退出。
  2. ko.validation.validateObservable__valid__可观测量设置为true并退出
    1.服务器返回无效响应,并执行callBack(false)
  3. callBack函数将__valid__设置为false
  4. ko.validation.validateObservable被告知__valid__可观测量的变化(callBack将其从true更改为false)。这基本上重复了上述步骤2。
    1.重复上述步骤3、4和5。
    1.由于可观察值没有改变,服务器返回另一个无效响应,触发上面的步骤6、7、8和9。
    1.我们有一场乒乓球比赛。
    所以看起来问题是ko.validation.validateObservable订阅处理程序不仅监听用户输入值的更改,而且监听其嵌套__valid__可观察值的更改。这是一个bug,还是我做错了什么?

次要问题

从上面的ko.validation源代码中可以看出,当服务器验证时,带有异步验证器的用户输入值被视为有效。因此,调用nestedModel2.isValid()不能被视为“真实”。相反,看起来我们必须使用isValidating钩子来创建对异步验证器的订阅,并且只在它们通知false的值之后才做出这些决定。这是设计的吗?与库的其他部分相比,这似乎是最违反直觉的,因为异步验证器没有isValidating可订阅,而且可以依靠.isValid()来讲述真相。这也是设计使然,还是我在这里也做错了什么?

mzillmmw

mzillmmw1#

因此,我提出的问题实际上与如何在ko. validation中使用异步验证器有关。从我的经验中,我学到了两大要点:
1.不要创建async Anonymous或一次性自定义规则验证器。相反,将它们创建为自定义规则。否则,您将以我的问题中描述的无限循环/ ping ping匹配结束。
1.如果你使用async验证器,不要信任isValid(),直到所有async验证器的isValidatingsubscriptions都变为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参数来告诉ko.validation验证是否成功,该调用将触发validated属性观察值上的isValidating观察值更改回false(意味着异步验证已经完成,并且现在知道输入是否有效)。
如果您的服务器端验证端点返回HTTP 200,则上述方法将起作用(OK)状态。这将导致执行.done函数,因为它等效于$.ajaxsuccess。如果服务器返回HTTP 400(Bad Request)状态,它将触发.fail函数执行。如果服务器返回一个自定义的验证消息,其中包含400、您可以从xhr.responseText获得它以有效地覆盖默认you suck because your input was wrong fix it or else消息。

pokxtpni

pokxtpni2#

我也遇到了同样的问题,把可观察性和有效性嵌套在一起。在self.errors = ko.validation.group(self.submissionAnswers, { deep: true, live: true });中,请注意特殊附加参数:包含字段live: true的对象

相关问题