let enrichableForms = [123,678];
let formsOnPage = [];
MktoForms2.whenReady( function(readyForm){
formsOnPage.push(readyForm.getFormId());
});
if( enrichableForms.some( id => formsOnPage.includes(id) ){
// load an external JS plugin
}
The code above was supposed to:
Except it doesn’t do that. formsOnPage will always be empty when checked. There’s no condition under which this code works as desired.
“OK,” you might say. “I get how, if loadForm is just before this code, the forms won’t be done loading when whenReady is called. Is there a race condition, so it would work if a form had loaded, like, 5 minutes before?”
Nope. There’s no race condition because the callback passed to whenReady always runs asynchronously. Doesn’t matter when forms become/became ready. Run this code:
console.log("banana");
MktoForms2.whenReady( function(readyForm){
console.log("jalapeno");
MktoForms2.whenReady( function(readyForm){
console.log("shishito");
})
console.log("ghost");
});
console.log("serrano");
100.00% of the time, the output will be:
⋖ banana
⋖ serrano
⋖ jalapeno
⋖ ghost
⋖ shishito
Remember, a race condition means factors like network quality, server response time, local processing power, disk & filesystem performance, and concurrently running code affect execution order.
That is, there must be an inadvertent “race” between components you can’t control — unlike here, where one code path always wins. Bugs related to execution order aren’t necessarily race conditions.
Shouldn’t expect MktoForms2.whenReady() to work any other way. Native addEventListener() never runs the callback immediately, either!
For example, if you add a DOMContentLoaded listener when DOM content was already loaded, the callback doesn’t run. If you add it when DOM content isn’t loaded yet, the callback runs later. There’s no case where it runs immediately upon being added.🞷
(Perhaps the author was half-remembering how jQuery.ready() works. But even there, the callback runs asynchronously; the special part is that it runs at all.)
With Marketo forms in particular, a race I sometimes see is loading the core library (forms2.min.js) asynchronously, yet not considering response time variance before accessing MktoForms2. Like this simplified example:
<form id="mktoForm_885"></form>
<script async src="https://123-ABC-456.mktoweb.com/js/forms2/js/forms2.min.js"></script>
<script src="https://someother.example.com/one.js"></script>
<script src="https://yetanother.example.net/two.js"></script>
<script>
if( window.MktoForms2 ) {
MktoForms2.loadForm("//123-ABC-456.mktoweb.com", "123-ABC-456", 885);
}
</script>
(In the real world, rather than an explicit async attribute, async is implied by using a tag manager like GTM to inject the library. Script-injected-scripts are always async.)
The code above is entirely dependent how long one.js + two.js take to load compared to forms2.min.js:
window.MktoForms2 fails and there won’t be a form.That fragility is obviously unacceptable.
Unfortunately, most developers (not just juniors) don’t test their code against adverse network/server conditions. If they did, this code would never make it to production. One of my testing habits is exaggerating the load time of async resources, say to a full 30 seconds, to make sure my code still works. (I run a special “slowpoke” server to help with this.)
This kind of race is obscured — but in no way solved! — when there’s a lot more than the two sync scripts one.js and two.js. There could be a stack of 20 JS/CSS files between forms2.min.js and the attempt to use MktoForms2, each subresource adding just enough consistent delay so the library finishes loading 99% of the time.
But go outside the comfort of your primary machine/connection and that’ll change, e.g. if you’re on a network where forms2.min.js takes 10ms more and other scripts take 10ms less.🞷🞷 That difference is invisible to the end user, but fatal to your code.
🞷 One native event-related feature that behaves a little weirdly is manually dispatching an Event. In this case, callbacks run synchronously to completion before the calling code continues. Surprises can ensue, as the same click listener is either blocking or non-blocking depending on whether it’s a trusted click or a synthetic MouseEvent. But addEventListener() is still asynchronous!
🞷🞷 Note 4xx and 5xx errors can be much faster than 2xx responses. Pages with tons of random analytics/enrichment/tracking plugins eventually end up with errors from outdated vendors. Also consider ad blockers, which deliver near-instantaneous errors. If you really want to stress-test your code, ensure that a 60μs (60 microsecond!) response from disposable code doesn’t have any effect on mission-critical code like forms.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.