If you don't want to use a callback, another async solution is explained in the recaptcha docs:
When loading reCAPTCHA asynchronously, keep in mind that reCAPTCHA
cannot be used until it has finished loading. For example, the
following code would likely break:
<script async src="https://www.google.com/recaptcha/api.js"></script>
<script>
// If reCAPTCHA is still loading, grecaptcha will be undefined.
grecaptcha.ready(function(){
grecaptcha.render("container", {
sitekey: "ABC-123"
});
});
</script>
In some situations, adjusting script ordering can be enough to prevent
race conditions. Alternatively, you can prevent race conditions by
including the following code snippet on pages that load reCAPTCHA. If
you are using grecaptcha.ready() to wrap API calls, add the following
code snippet to ensure that reCAPTCHA can be called at any time.
<script async src="https://www.google.com/recaptcha/api.js"></script>
<script>
// How this code snippet works:
// This logic overwrites the default behavior of `grecaptcha.ready()` to
// ensure that it can be safely called at any time. When `grecaptcha.ready()`
// is called before reCAPTCHA is loaded, the callback function that is passed
// by `grecaptcha.ready()` is enqueued for execution after reCAPTCHA is
// loaded.
if(typeof grecaptcha === 'undefined') {
grecaptcha = {};
}
grecaptcha.ready = function(cb){
if(typeof grecaptcha === 'undefined') {
// window.__grecaptcha_cfg is a global variable that stores reCAPTCHA's
// configuration. By default, any functions listed in its 'fns' property
// are automatically executed when reCAPTCHA loads.
const c = '___grecaptcha_cfg';
window[c] = window[c] || {};
(window[c]['fns'] = window[c]['fns']||[]).push(cb);
} else {
cb();
}
}
// Usage
grecaptcha.ready(function(){
grecaptcha.render("container", {
sitekey: "ABC-123"
});
});
</script>