-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix for race condition in readystate detection #1972
Conversation
Do you have any evidence that there is a race condition here? I don't think it's possible for
It's certainly possible I missed something. More discussion here: #1688 |
I have a page with this issue, it is absolutely possible. It is a fairly large page using the script.js async loader, so I don't have a portable example ready. But I have confirmed htmx is loading after |
What is the script.js asnyc loader? That sounds like the issue. Can you provide a minimal code sample? |
The script loader (https://github.com/ded/script.js) almost definitely is the issue, but that's kind of beside the point. There are a lot of weird ways to get javascript onto a page and htmx should work with all of them. I will try and get a minimal example, but again, there is a real issue and this fixes it. |
The issueLooking at
So theoretically, there is a time frame between the moment the Looking at htmx' code: Lines 3727 to 3730 in 953ce54
Line 3742 in 953ce54
That means, in this specific scenario, because of async loading, htmx will bind to I agree with @alexpetros that you would normally not want to load htmx async, as the "interactive" state would not be interactive after all if htmx has not loaded yet (which means all your As for the PRThis PR works I think because the
I was going to suggest replacing
Though as per the HTML spec
So technically, there is a little time frame in between the moment the Let me know what you think about this |
The issue with changing the ready state check to If it helps, here is a scratchpad with an example where htmx will consistently fail to initialize due to the ordering of |
If we want to keep the same performance in the usual use case, we could listen for both |
Thanks for the codepen!
You're right, I had completely forgotten about this, what a nightmare those initialization events are ; nevermind what I suggested indeed!
I agree, though we should probably listen for Edit: that's precisely what you suggested, wow am I bad at reading |
It's beside the point only if we can accommodate it without regressions to the other ways that we currently support of getting JavaScript onto a page. Generally speaking, the "correct" way to use htmx is use a script tag, and as @Telroshan points out, we also allow using the I am particularly concerned about performance regressions, and also regressing on #1688. Because of the close-but-not-exactly relationship between the document readystate and the events firing, which @Telroshan laid out in detail above, this cycle is quite tricky to get correct.
I obviously can't promise that it would be accepted, but I would like to see it if you're willing. |
As discussed, I pushed changes to check both readystate and DOMContentLoaded. Whichever happens first will consume the pending initialization events. |
i hate javascript so much |
i didn't really grok the original change alex made at the time, but reading through the docs a bit more it seems to me we might be able to do something like this: function ready(fn) {
if (document.state === "loading") {
// trigger on the next readystatechange event which will be at least 'interactive'
getDocument().addEventListener('readystatechange', fn, {once:true});
} else {
fn();
}
} where we use the readystate events rather than DomContentLoaded. i'm a total newb to all this, but my understanding is that the 'interactive' state is where we can start doing all our DOM processing, event handlers, etc. and this would narrow things down to just that window beetween loading and interactive. please let me know what you think and also consider that javascript must be destroyed |
Verified on Chrome, Safari and Firefox OSX that |
updated proposed function ready(fn) {
if (document.state === "loading") {
// trigger on the next readystatechange event which will be at least 'interactive'
getDocument().addEventListener('readystatechange', fn, {once:true});
} else {
fn();
}
} |
Yeah that's what I thought at first too @1cg, but as @ahollandECS correctly pointed out, that
So that line if (document.state === "loading") would cause that issue again as htmx would initialize right away, i.e. "too soon" From what I understand with this past PR, even though the state was already "interactive", the |
OK, @ahollandECS I'm sorry it took me a while to get up to speed, but @Telroshan walked me through everything. What do you think about the following code: function ready(functionToCall) {
// if the page is complete, go ahead and call it
if (getDocument().readyState === 'complete') {
functionToCall();
} else {
// otherwise listen for DOMContentLoaded *and* readystatechange to "complete"
// and call the function once in whichever event comes first
getDocument().addEventListener('DOMContentLoaded', function() {
if(functionToCall){
functionToCall()
functionToCall = null;
}
});
getDocument().addEventListener('readystatechange',
if(functionToCall && getDocument().readyState === 'complete'){
functionToCall()
functionToCall = null;
});
}
} @Telroshan walked me through the spec on the event and script ordering, and we thing this covers all our bases, and it encrapsulates the ugly logic within the function itself. It's ugly, but encrapsulated. |
@ahollandECS thoughts on above? |
It's genuinely insane that the variable you can check for the document state doesn't match up with the events that are fired. Anyway, I support that solution, think the additional closure is worth it. |
I pushed an update, encapsulating everything inside |
beauty! thank you for your work on this! |
This reverts commit 78a9ecf.
This reverts commit 78a9ecf.
This is an attempt to address the situation where the
DOMContentLoaded
event has already fired, butreadyState
has not yet reached "complete" (for example it can be "interactive"). In this case initialization will never happen becauseDOMContentLoaded
will never fire again. If we're looking for a "complete" ready state, theDOMContentLoaded
event is irrelevant, we should be looking for ready state changes.