fix: prevent race conditions in htmx boost response handling

This commit is contained in:
Björn Fromme
2026-02-20 17:24:20 +01:00
parent 39bacd2927
commit cec481741b
+28 -6
View File
@@ -6,6 +6,7 @@ const DEBOUNCE_MS = 200
let debounceTimeout = null
let historyRestoreGracePeriod = false
let savedScrollPosition = null
let activeRequests = 0
function getIndicator() {
return document.getElementById(INDICATOR_ID)
@@ -35,6 +36,8 @@ function handleBeforeRequest(event) {
return
}
activeRequests++
// Save scroll position of the target container before HTMX request
const target = event.detail?.target || document.getElementById('main-content')
if (target && typeof target.scrollTop === 'number') {
@@ -52,6 +55,19 @@ function handleBeforeRequest(event) {
}, DEBOUNCE_MS)
}
function handleRequestEnd() {
// Counter may already be 0 if a force-reset (history save, page show) fired
// before this afterRequest — ignore the stale completion in that case
if (0 === activeRequests) {
return
}
activeRequests--
if (0 === activeRequests) {
hide()
}
}
function handleAfterSwap() {
// Restore scroll position after HTMX swaps content
if (savedScrollPosition !== null && savedScrollPosition.element) {
@@ -61,6 +77,7 @@ function handleAfterSwap() {
}
function handleHistoryRestore() {
activeRequests = 0
hide()
// Set grace period to ignore change events triggered by history restore
@@ -71,24 +88,29 @@ function handleHistoryRestore() {
}
function handleTimeout() {
hide()
// Don't decrement here — htmx:afterRequest always fires alongside htmx:timeout
// and handles the counter. This handler only shows the alert.
alert('Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut. Falls das Problem weiterhin besteht, kontaktieren Sie bitte unseren Support.')
}
function handleBeforeHistorySave() {
// Hide indicator before HTMX saves the page to history cache
activeRequests = 0
hide()
}
function handlePageShow() {
activeRequests = 0
hide()
}
// Initialize event listeners
document.body.addEventListener('htmx:beforeRequest', handleBeforeRequest)
document.body.addEventListener('htmx:afterRequest', hide)
document.body.addEventListener('htmx:afterRequest', handleRequestEnd)
document.body.addEventListener('htmx:afterSwap', handleAfterSwap)
document.body.addEventListener('htmx:timeout', handleTimeout)
document.body.addEventListener('htmx:historyRestore', handleHistoryRestore)
document.body.addEventListener('htmx:sendError', hide)
document.body.addEventListener('htmx:responseError', hide)
document.body.addEventListener('htmx:beforeHistorySave', handleBeforeHistorySave)
window.addEventListener('pageshow', hide)
window.addEventListener('popstate', handleHistoryRestore)
window.addEventListener('pageshow', handlePageShow)
window.addEventListener('popstate', handleHistoryRestore)