Dashboard: Add abort controller to abort in-flight request on navigation

Known bug: abort controller logic doesn't work properly in firefox
 because a general network error is fired before the abort callback.
 We would probably need to track the navigation-click event to overcome
 this.

Signed-off-by: Serhii Orlivskyi <serhii.orlivskyi@cedalo.com> (Cedalo GmbH)
This commit is contained in:
Serhii Orlivskyi
2026-01-13 00:29:10 +00:00
committed by Roger Light
parent 502d2dcc19
commit 9fc336b894
3 changed files with 44 additions and 10 deletions
+15 -5
View File
@@ -1,5 +1,8 @@
class MosquittoDashboard {
constructor(headless = false) {
this.abort = new AbortController();
registerAbortController(this.abort, this);
this.headlessMode = !!headless;
!this.headlessMode && window.Chart.register(window.ChartZoom); // chartjs comes from lib/
@@ -1483,7 +1486,10 @@ class MosquittoDashboard {
const nowTimestampMilliseconds = new Date().getTime();
let sysTopics = null;
try {
sysTopics = await fetchData(SYSTOPIC_ENDPOINT);
sysTopics = await fetchData(SYSTOPIC_ENDPOINT, {
signal: this.abort.signal,
cache: "no-store",
});
this.version = sysTopics?.["$SYS/broker/version"];
this.previousDataFetchFailed = false;
@@ -1492,12 +1498,16 @@ class MosquittoDashboard {
this.setBrokerStatus();
} catch (error) {
const errorMsg = `Error fetching sys topics: ${error?.message}`;
console.error(errorMsg);
if (this.abort.signal.aborted || error?.name === "AbortError") {
console.log("Fetching systopics aborted");
} else {
console.error(errorMsg);
if (!this.previousDataFetchFailed) {
alert(errorMsg);
}
}
this.brokerOnline = false;
this.setBrokerStatus();
if (!this.previousDataFetchFailed) {
alert(errorMsg);
}
this.previousDataFetchFailed = true;
}
if (!sysTopics) {
+16 -3
View File
@@ -1,15 +1,28 @@
class Listeners {
constructor() {
this.abort = new AbortController();
registerAbortController(this.abort, this);
this.init();
}
async init() {
try {
const listeners = await fetchData(LISTENERS_ENDPOINT);
const listeners = await fetchData(LISTENERS_ENDPOINT, {
signal: this.abort.signal,
cache: "no-store",
});
this.displayListeners(listeners);
} catch (error) {
console.error("Error fetching listeners:", error);
alert(`Error loading listeners: ${error}`);
if (
this.pageHiding ||
this.abort.signal.aborted ||
error?.name === "AbortError"
) {
console.log("Fetching listeners aborted");
} else {
console.error("Error fetching listeners:", error);
alert(`Error loading listeners: ${error}`);
}
}
}
+13 -2
View File
@@ -19,13 +19,16 @@ function toAsyncAndWaitAfter(task, delay = 0) {
};
}
async function fetchData(endpoint) {
async function fetchData(endpoint, opts = {}) {
if (!endpoint) {
throw new Error("No endpoint provided to fetch data function");
}
let data;
const res = await fetch(endpoint);
const res = await fetch(endpoint, {
...opts,
headers: { Accept: "application/json" },
});
if (res.ok) {
data = await res.json();
} else {
@@ -165,3 +168,11 @@ async function copyToClipboard(textToCopy) {
function isMobile() {
return window.innerWidth < 1024;
}
function registerAbortController(abortController) {
// in firefox the below doesn't help unfortunately: a general netrowk error is being thrown even before the below callback is executed. A proper implementation would require aborying in-flight requets right before the navigation but it's not worth the effort. Currently you will see an alert for a quick moment when spam-clicking onto the "listern" tab in the sidebar on firefox
const abortCallback = () => {
abortController.abort();
};
window.addEventListener("pagehide", abortCallback, { once: true });
}