jjzjj

javascript - knockout 验证异步验证器 : Is this a bug or am I doing something wrong?

coder 2024-05-06 原文

我真的很喜欢Eric Barnard's knockout validation lib与 observable 集成,允许分组,并提供自定义验证器可插入性(包括即时验证器)。有几个地方可以使用户体验更加灵活/友好,但总体而言,它的文档相当齐全... 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再次调用的函数。 我破解了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__是一个可观察的,它被设置为 truevalidateAsync函数退出。现在我们来看看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函数是作为第三个参数传递给异步的 validatorMyViewModel 中声明的函数.然而在这发生之前,一个 isValidating observable 的订阅者被调用来通知异步验证已经开始。当服务器调用完成时,回调被调用(在这种情况下只传递 true 或 false)。

现在这就是 MyViewModel 中的断点的原因当服务器端验证失败时导致无限循环:在 callBack 中上面的函数,注意 __valid__当验证失败时,observable 设置为 false。这是发生的事情:
  • 无效的用户输入更改了 nestedModel2.prop2可观察的。
  • ko.validation.validateObservable通过订阅通知此更改。
  • validateAsync函数被调用。
  • 调用自定义异步验证器,它提交异步 $.ajax调用服务器并退出。
  • ko.validation.validateObservable 设置 __valid__可观察到 true并退出 .
  • 服务器返回无效响应,并且 callBack(false)被执行。
  • callBack功能集__valid__false .
  • ko.validation.validateObservable通知 __valid__ 的更改observable ( callBack 将其从 true 更改为 false ) 这基本上重复了上面的步骤 2。
  • 重复上述步骤 3、4 和 5。
  • 由于 observable 的值没有改变,服务器返回另一个无效响应,触发上面的步骤 6、7、8 和 9。
  • 我们有自己的乒乓球比赛。

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

    次要问题

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

    最佳答案

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

  • 不创建 async Anonymous or Single-Use Custom Rule validators .相反,将它们创建为 Custom Rules .否则,您最终会遇到我的问题中描述的无限循环/ping 匹配。
  • 如果您使用 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() });
        }
    };
    

    您也可以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 验证是否成功的函数。该调用将触发 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信息。

    关于javascript - knockout 验证异步验证器 : Is this a bug or am I doing something wrong?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12721951/

    有关javascript - knockout 验证异步验证器 : Is this a bug or am I doing something wrong?的更多相关文章

    1. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

      我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

    3. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

      我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

    4. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

      exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

    5. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

      我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

    6. ruby-on-rails - 如何将验证与模型分开 - 2

      我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

    7. ruby-on-rails - 跳过状态机方法的所有验证 - 2

      当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

    8. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

      我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

    9. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    10. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

      在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

    随机推荐