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

2024-02-10

我真的很喜欢如何Eric Barnard 的淘汰赛验证库 https://github.com/Knockout-Contrib/Knockout-Validation与可观察量集成,允许分组,并提供自定义验证器可插拔性(包括动态验证器)。有几个地方可以使用户体验更加灵活/友好,但总的来说,它有相当详细的文档记录......在我看来,除了异步验证器 https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Async-Rules.

今天我在进行搜索之前花了几个小时来解决这个问题登陆此 https://github.com/Knockout-Contrib/Knockout-Validation/issues/145. I think我和原作者有同样的问题,但同意目前尚不清楚 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用户输入是好还是坏。如果您在指示的行上放置断点并使用已知的无效值触发验证,则最终会出现无限循环,其中 ajaxsuccess函数导致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__是一个可观察量,并且它被设置为true之后validateAsync函数退出。现在我们来看看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 并退出。这callBackfunction 是作为第三个参数传递给 async 的函数validator函数声明于MyViewModel。然而在此之前,一个isValidating调用 observable 的订阅者来通知异步验证已经开始。当服务器调用完成时,将调用回调(在本例中仅传递 true 或 false)。

现在这就是为什么断点MyViewModel当服务器端验证失败时,会导致无限的乒乓循环:callBack上面的函数,注意如何__valid__当验证失败时 observable 设置为 false。发生的情况如下:

  1. 无效的用户输入改变了nestedModel2.prop2可观察到的。
  2. The ko.validation.validateObservable通过订阅此更改来通知。
  3. The validateAsync函数被调用。
  4. 调用自定义异步验证器,该验证器提交异步$.ajax调用服务器并退出。
  5. The ko.validation.validateObservable 设置__valid__可观察到true并退出.
  6. 服务器返回无效响应,并且callBack(false)被执行。
  7. The callBack功能集__valid__ to false.
  8. The ko.validation.validateObservable被通知更改__valid__可观察到的(callBack改变它从true to false) 这基本上重复了上面的步骤 2。
  9. 重复上述步骤 3、4 和 5。
  10. 由于 observable 的值没有改变,服务器返回另一个无效响应,触发上面的步骤 6、7、8 和 9。
  11. 我们自己有一场乒乓球比赛。

所以看来问题是ko.validation.validateObservable订阅处理程序不仅监听用户输入值的更改,还监听其嵌套值的更改__valid__可观察到的。这是一个错误,还是我做错了什么?

一个次要问题

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


所以我问的问题确实与如何在 ko.validation 中使用异步验证器有关。我从我的经历中学到了两个重要的教训:

  1. 不要创建async 匿名或一次性自定义规则验证器 https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Custom-Validation-Rules#anonymous-or-single-use-custom-rules。相反,将它们创建为自定义规则 https://github.com/Knockout-Contrib/Knockout-Validation/wiki/Custom-Validation-Rules#custom-rules。否则,您最终会遇到我的问题中描述的无限循环/乒乓球比赛。

  2. 如果你使用async验证者,不要相信isValid()直到所有async验证者的isValidating subscriptions更改为假。

如果您有多个异步验证器,则可以使用如下模式:

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() });
    }
};

你也可以请参阅此以获取具有类似替代解决方案的另一个参考 https://github.com/Knockout-Contrib/Knockout-Validation/issues/145.

以下是异步“自定义规则”的示例:

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 });
        });
    }
};

基本上,您使用callbackarg 到validator函数告诉 ko.validation 验证是否成功。该调用将触发isValidating已验证属性 observables 上的 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信息。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

淘汰赛验证异步验证器:这是一个错误还是我做错了什么? 的相关文章

随机推荐