Javascript 淘汰验证多个自定义异步规则

Javascript 淘汰验证多个自定义异步规则,javascript,validation,knockout.js,knockout-validation,Javascript,Validation,Knockout.js,Knockout Validation,我有一个域属性,我想验证两件事 URL存在(可访问) URL存在于我的本地数据库中 为了检查这些内容,我创建了异步验证规则,并在我的属性上应用了它们 发生的情况是,每次来自其中一个规则的响应出现得更早,并且它将isValidating属性设置为false,我希望该属性为true,直到来自第二个规则的响应出现 自定义规则: export function enableCustomValidators() { (ko.validation.rules as any)["urlValidat

我有一个域属性,我想验证两件事

  • URL存在(可访问)
  • URL存在于我的本地数据库中
  • 为了检查这些内容,我创建了异步验证规则,并在我的属性上应用了它们

    发生的情况是,每次来自其中一个规则的响应出现得更早,并且它将
    isValidating
    属性设置为
    false
    ,我希望该属性为
    true
    ,直到来自第二个规则的响应出现

  • 自定义规则:

     export function enableCustomValidators() {
        (ko.validation.rules as any)["urlValidationServicePath"] = {
        async: true,
        validator: function (url: string, baseUrl: string, callback: any) {
            getRequest(url, baseUrl, callback, "true");
        },
        message: 'You must enter a reachable domain.',
    },
    (ko.validation.rules as any)["customerValidationServicePath"] = {
        async: true,
        validator: function (url: string, baseUrl: string, callback: any) {
            getRequest(url, baseUrl, callback, "false");
        },
        message: "This url already exists in our system. Please contact us at hello@ve.com",
    }
    
    ko.validation.registerExtenders();
    }
    
    function getRequest(url: string, baseUrl: string, callback: any, method: string) {
        var restClient = new RestClient();
        restClient.downloadString(baseUrl.concat(url), (responseText) => {
            method === "true" ? callback(responseText === "true" ? true : false) :
                callback(responseText === "true" ? false : true);
    });
    }
    
  • 规则的使用:

    export class CompanySetupVM extends BasePageVM {
        public websiteUrl: KnockoutObservable<string> = ko.observable(undefined);
        public isValidating: KnockoutObservable<boolean> = ko.observable(false);
    
        public constructor() {
            this.websiteUrl.extend({
                required: {
                params: true,
                message: CompanySetupVM.ErrorMessageNullWebsiteUrl
            },
            urlValidationServicePath: CompanySetupVM.DomainValidationPath,
            customerValidationServicePath: CompanySetupVM.CustomerValidationPath
            });
            this.isValidating = ko.computed(() => this.websiteUrl.isValidating(), this);   
        }
    }
    

  • 我已经查看了knockout validation()的源代码,很明显不支持两个独立的异步验证器

    一旦异步规则开始运行,
    isValidating
    属性就会设置为
    true
    ,一旦该规则完成,就会再次设置为
    false
    。因此,多个异步规则冲突

    只有一个解决办法。删除第二个异步验证程序

    您可以在客户端或服务器端将两个检查折叠为一个

    要在客户端执行此操作,您需要编写一个验证器,它运行两个Ajax请求,并仅在两个请求都返回后调用验证
    回调

    要在服务器端执行此操作,必须先连续运行“可访问”和“在数据库中”检查,然后才能向客户端提供总体响应

    我个人更喜欢改变服务器端,因为

  • 它使客户机代码保持整洁和易于管理
  • 每次检查可以节省一次HTTP往返
  • 从语义上讲,URL检查是一件由于多种原因而失败的事情
  • 让服务器发送自定义验证结果和消息很容易
  • 除了纯
    true
    false
    之外,验证插件还可以理解以下格式的响应:

    {isValid: false, message: "something is wrong"}
    
    因此,让您的服务器发送带有适当验证结果和错误消息的JSON响应,让您的REST客户端下载JSON而不是文本

    然后,您只需将服务器的响应直接传递给验证回调

    ko.validation.rules.urlValidationServicePath = {
        async: true,
        validator: function (url, baseUrl, callback) {
            restClient.downloadJSON(baseUrl.concat(url), callback);
        },
        message: 'The URL you entered is not valid.'
    };
    

    此处的
    消息
    仅为默认值。服务器的
    消息始终优先于验证规则中的设置。

    是,正如Tomalak指出的,不可能有多个异步验证器。但我在客户端解决了这个问题,而且这个解决方案非常易于管理和灵活。 这里的诀窍是实现不同的异步验证器作为常规的淘汰扩展器,并使用单个异步规则来调用它们。以下是异步规则:

    interface HasAsyncValidator {
        asyncValidators: Validator[];
    }
    
    interface Validator {
        name: string,
        validator: (params: any) => boolean | PromiseLike<any>,
        params: any
    }
    
    interface KnockoutObservable<T> extends HasAsyncValidator {}
    
    ko.validation.rules["validateAsync"] = {
    validator: async (value: any, paramsAccessor: () => HasAsyncValidator, callback: (result: boolean | ValidationResult) => void) => {
        const params = paramsAccessor();
        if (!params || !params.asyncValidators) {
            callback(true);
            return;
        }
    
        try {
            const results = await Promise.all(params.asyncValidators.map(v => v.validator(v.params)));
            const invalidResult = results.find(r => r.isValid === false);
            callback(!!invalidResult ? invalidResult : true);
        } catch (error) {
            callback(false);
            throw error;
        }
    },
    message: 'default message',
    async: true
    }
    

    注意,我们应该将值访问器传递给validateAsync,而不是值本身。这是必需的,这样异步规则就不会错过稍后可以添加的验证器。

    您需要显示代码。没有人能通过集中精力来指出你的错误。我已经添加了代码
    interface HasAsyncValidator {
        asyncValidators: Validator[];
    }
    
    interface Validator {
        name: string,
        validator: (params: any) => boolean | PromiseLike<any>,
        params: any
    }
    
    interface KnockoutObservable<T> extends HasAsyncValidator {}
    
    ko.validation.rules["validateAsync"] = {
    validator: async (value: any, paramsAccessor: () => HasAsyncValidator, callback: (result: boolean | ValidationResult) => void) => {
        const params = paramsAccessor();
        if (!params || !params.asyncValidators) {
            callback(true);
            return;
        }
    
        try {
            const results = await Promise.all(params.asyncValidators.map(v => v.validator(v.params)));
            const invalidResult = results.find(r => r.isValid === false);
            callback(!!invalidResult ? invalidResult : true);
        } catch (error) {
            callback(false);
            throw error;
        }
    },
    message: 'default message',
    async: true
    }
    
    ko.extenders["validationRule"] = (target: any, option: any) => {
        const validatorObj: Validator = {
            name: "validationRule",
            params: option,
            validator: async (): Promise<boolean | ValidationResult> => {
                const unwrappedValue = ko.unwrap(target);
                const result = await callServer();
                return {
                    isValid: result.isValid,
                    message: result.message
                };
            }
        }
        addOrUpdateAsyncValidator(target, validatorObj);
    };
    
    function addOrUpdateAsyncValidator(target: HasAsyncValidator, validatorObj: Validator) {
        target.asyncValidators = target.asyncValidators || [];
        
        const existingRule = target.asyncValidators.find(v => v.name == validatorObj.name);
        !!existingRule
            ? existingRule!.params = validatorObj.params
            : target.asyncValidators.push(validatorObj);
    } 
    
    let value = ko.observable();
    value.extend({ validationRule: true, validateAsync: () => value });