提交表单之前使用ajax验证字段

时间:2018-06-21 13:43:36

标签: javascript ajax forms validation promise

我刚刚开始创建表单验证器,遇到一个令人费解的问题。如果我要验证一个简单字段(例如密码),则可以仅在客户端执行此操作(不是说我不会在服务器上对其进行验证)。我的意思是,我可以在客户端计算机上进行验证,而无需任何外部检查。

如果我要验证更复杂的字段(用户名),则需要进行外部检查。例如,在注册表格中,我要验证用户名(确保用户名不存在)。为此,我必须进行ajax调用。这使事情变得有些复杂。检查下面的代码。

FormValidator.prototype.validate = function validateForm() {
    this.errors = {};
    for (var fieldName in this.fields) {
        var field = this.fields[fieldName];

        if (field.hasOwnProperty("required") && field.required && field.elem.value.length == 0) {
            this.addError(fieldName, "This field is required.");
            break;
        }

        if (field.hasOwnProperty("minLength") && field.elem.value.length < field.minLength) {
            this.addError(fieldName, "Input length should not be less than  " + field.minLength + " characters.");
            break;
        }

        if (field.hasOwnProperty("maxLength") && field.elem.value.length > field.maxLength) {
            this.addError(fieldName, "Input length should not be greater than" + field.maxLength + " characters.");
            break;
        }

        if (field.hasOwnProperty("ajax")) {
            // FormValidator can't possibly know what the call will return, so we can't add the error here
            // it has to be done manually
            field.ajax(this, field, fieldName);
        }
    }
    if (this.errors.length != 0) {
        // warn the user
        console.log(this.errors);
    }
};

var fv = new FormValidator(document.forms[0]);

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax      : function (fv, field, fieldName) {
        ajax("http://localhost/surec/scripts/user_check.php?field=login_name&value=" + field.elem.value, {
            success : function () {
                var response = JSON.parse(this.response);
                // manually adding the error
                if (!response.error && response.exists) {
                    fv.addError(fieldName, "This username is taken.");
                }
            },
            // async: false,
        });
    },
});

// called on form submit
// fv.validate();

在调用fv.validate()时(假设用户输入了已使用的用户名),validate()不会采取任何措施来警告用户,因为ajax调用是异步的。 if (this.errors.length != 0) {完成错误检查后,错误将为空。当ajax调用完成时,将填充它,然后为时已晚。

要解决此问题,我可以使ajax调用同步。这解决了问题,但是我不确定使用同步调用。这种方法有效吗?或者我可以采取其他方法吗?


更新:我已经开始研究Promise了,我想我已经掌握了它。我已经使其工作到一定程度,所以我仍然需要一些帮助。

我要尝试做的图形描述如下:

enter image description here

我创建了一个异步循环函数,将用于循环这些字段:

async function asyncForEach(array, callback) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
    }
}

以及validate函数的当前状态:

FormValidator.prototype.validate = async function validateForm() {
    this.errors = {};
    var self = this;

    await asyncForEach(self.fields.keys(), async function (fieldName) {
        var field = self.fields[fieldName];

        if (field.hasOwnProperty("required") && field.required && field.elem.value.length == 0) {
            self.addError(fieldName, "This field is required.");
            // break;
        }

        if (field.hasOwnProperty("minLength") && field.elem.value.length < field.minLength) {
            self.addError(fieldName, "Input length should not be less than " + field.minLength + " characters.");
            // break;
        }

        if (field.hasOwnProperty("maxLength") && field.elem.value.length > field.maxLength) {
            self.addError(fieldName, "Input length should not be greater than " + field.maxLength + " characters.");
            // break;
        }

        if (field.hasOwnProperty("ajax")) {
            // FormValidator can't possibly know what the call will return, so we can't add the error here
            // it has to be done manually
            await field.ajax(self, field, fieldName);
        }

    });

    if (self.errors.length() != 0) {
        // warn the user
        console.log("errors: ", self.errors);
    }
};

这似乎可行(在测试用例中)。见下文。我没有使用ajax函数,而是伪造了它(使用了1秒的延迟)。 console.log("errors: ", self.errors);中的fv.validate()在完成外部检查(假ajax)(延迟1秒)后运行。

var fv = new FormValidator(document.forms[0]);

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax : async function (fv, field, fieldName) {
        // assume delay is ajax
        await delay(1000);
        // and on success (and login_name collision) we add an error
        fv.addError(fieldName, "This username is taken.");
    },
});

var delay = (ms) => new Promise(r => setTimeout(r, ms));

fv.validate();

现在,我需要重写外部检查方法(ajax)并使它与该代码一起使用。我尝试了几种组合(可能是废话,使用试验错误),但无法使其正常工作。我应该如何进行?

FormValidator.js(GitHub存储库)

2 个答案:

答案 0 :(得分:0)

这里有一个可能的实现承诺。这是一个简化的示例,仅专注于管理一系列错误(其中一个错误是ajax调用的结果)

// errors is an array of promises
const errors = ["Too long"] 

const remoteErrorCheck = $.ajax({type:"GET", url:'https://api.stackexchange.com/2.2/info?site=stackoverflow'}).then(function(data){ 
    // here you have access to the result of ajax request, inside data
    return Promise.resolve("error or ok")
    // you want to return a promise to put in the array of promises
})

// now put the promise from the ajax request into the array of promises
errors.push(remoteErrorCheck)

// when all the promises are resolved, then do something with the results
Promise.all(errors).then((a)=>{
  console.log(a)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

请注意,Promise.all在执行前要等待ajax完成。 Promise.all将字符串视为已解析的承诺。

由于remoteErrorCheck已经有一个.then并且已经执行了一个,并且由于它返回了一个承诺,所以Promise of Promise被压缩为一个承诺,这就是为什么您可以访问{错误数组中的{1}}字符串

这需要适应您的情况(对象而不是字符串,error or ok内部的验证函数应该从外部传递,...),但这应该是一个很好的起点

答案 1 :(得分:0)

我设法解决了这个问题。我决定不使用自定义的Ajax函数/ lib,而是使用fetch

单独使用(不等待)是行不通的,所以我不得不添加它。现在就可以了。

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax : async function (fv, field, fieldName) {
        await fetch("http://localhost/surec/scripts/user_check.php?field=login_name&value=" + field.elem.value)
        .then(function (response) {
            return response.json();
        }).then(function (response) {
            if (!response.error && response.exists) {
                fv.addError(fieldName, "This username is taken.");
            }
        });
    },
});

尽管目前可以,但是我不确定这将来是否会引起问题。如果可以,我将更新此质量检查。