Johan Fredrik Varen
Johan Fredrik Varen

Reputation: 3914

How do I validate an HTML form using async JS validators

My question mentions an HTML form, but the crux of it pertains to how an arbitrary number of async JS functions can contribute to a single value – in the case of an HTML form, whether it's valid or not.

Being an old synchronous backend dinosaur, completely inexperienced with the use of JS promises/async-await, I can't seem to wrap my head around how to accomplish this.

Consider this validation class:

class Validation {
    construct() {
        this.validators = [];
    }

    register(validator) {
        this.validators.push(validator);
    }

    validate(form) {
        let valid = true; // Benefit of the doubt
        this.validators.forEach(function(validator) {
            valid = validator(form) ? valid : false;
        });
        return valid;
    }
}

Say I want to register 3 validators:

const validation = new Validation();

validation.register(form => {
    // Return true if all required form fields have values, false otherwise
});

validation.register(form => {
    // Return true if the user has made any changes to the form, false otherwise
});

validation.register(form => {
    // Do an AJAX request and return true/false depending on the response
});

And then check to see if the form is valid, disabling or enabling its submit button accordingly:

submitButton.setAttribute('disabled', !validation.validate(form));

All is fine for the first two validator functions, as they don't exhibit any asynchronous behavior. But the third validator function performs an AJAX call, and that is where it all falls apart.

I assume the answer to the problem lies along the lines of re-writing the whole validate() method to somehow call the validator functions in an asynchronous manner. Ensuring that the submit button is enabled only if all the validators have returned true (in their sweet time) is what's really baking my noodle.

Would someone be so kind as to magically transform my naive approach into something async friendly?

Upvotes: 1

Views: 1196

Answers (1)

Ruan Mendes
Ruan Mendes

Reputation: 92304

Just await the validators in a loop, and you can probably exit early once you find an error. Your function must become async.

Each validator can return a Promise<boolean> or just a boolean

async validate(form) {
    for (const validator of this.validators)
        if (!(await validator(form)) {
           return false;
        }
    }
    return true;
}

// Somewhere else
const isValid = await validation.validate(form);
submitButton.setAttribute('disabled', !isValid);

Note that you cannot do it with Array#forEach since it will not wait for each iteration if it returns a promise. You also cannot return early from an Array#forEach


Example of an async validator.

const validation = new Validation();

validation.register(form => {
    return fetch("/validation")
        .then(response => response.json())
        .then(body => body.isValid)

});

Upvotes: 3

Related Questions