fix: abort ongoing requests when changing pages (#3927)

This commit is contained in:
manx98 2025-06-29 15:38:03 +08:00 committed by GitHub
parent 2d1a82b73f
commit 93c4b2e03c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 24 deletions

View File

@ -2,14 +2,22 @@ import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { baseURL } from "@/utils/constants";
import { upload as postTus, useTus } from "./tus";
import { createURL, fetchURL, removePrefix } from "./utils";
import { createURL, fetchURL, removePrefix, StatusError } from "./utils";
export async function fetch(url: string) {
export async function fetch(url: string, signal?: AbortSignal) {
url = removePrefix(url);
const res = await fetchURL(`/api/resources${url}`, { signal });
const res = await fetchURL(`/api/resources${url}`, {});
const data = (await res.json()) as Resource;
let data: Resource;
try {
data = (await res.json()) as Resource;
} catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name === "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw e;
}
data.url = `/files${url}`;
if (data.isDir) {
@ -205,10 +213,18 @@ export function getSubtitlesURL(file: ResourceItem) {
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
}
export async function usage(url: string) {
export async function usage(url: string, signal: AbortSignal) {
url = removePrefix(url);
const res = await fetchURL(`/api/usage${url}`, {});
const res = await fetchURL(`/api/usage${url}`, { signal });
return await res.json();
try {
return await res.json();
} catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name == "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw e;
}
}

View File

@ -6,7 +6,8 @@ import { encodePath } from "@/utils/url";
export class StatusError extends Error {
constructor(
message: any,
public status?: number
public status?: number,
public is_canceled?: boolean
) {
super(message);
this.name = "StatusError";
@ -33,7 +34,11 @@ export async function fetchURL(
},
...rest,
});
} catch {
} catch (e) {
// Check if the error is an intentional cancellation
if (e instanceof Error && e.name === "AbortError") {
throw new StatusError("000 No connection", 0, true);
}
throw new StatusError("000 No connection", 0);
}

View File

@ -129,6 +129,7 @@ import {
import { files as api } from "@/api";
import ProgressBar from "@/components/ProgressBar.vue";
import prettyBytes from "pretty-bytes";
import { StatusError } from "@/api/utils.js";
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
@ -136,7 +137,7 @@ export default {
name: "sidebar",
setup() {
const usage = reactive(USAGE_DEFAULT);
return { usage };
return { usage, usageAbortController: new AbortController() };
},
components: {
ProgressBar,
@ -157,6 +158,9 @@ export default {
},
methods: {
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
abortOngoingFetchUsage() {
this.usageAbortController.abort();
},
async fetchUsage() {
const path = this.$route.path.endsWith("/")
? this.$route.path
@ -166,13 +170,18 @@ export default {
return Object.assign(this.usage, usageStats);
}
try {
const usage = await api.usage(path);
this.abortOngoingFetchUsage();
this.usageAbortController = new AbortController();
const usage = await api.usage(path, this.usageAbortController.signal);
usageStats = {
used: prettyBytes(usage.used, { binary: true }),
total: prettyBytes(usage.total, { binary: true }),
usedPercentage: Math.round((usage.used / usage.total) * 100),
};
} catch (error) {
if (error instanceof StatusError && error.is_canceled) {
return;
}
this.$showError(error);
}
return Object.assign(this.usage, usageStats);
@ -200,5 +209,8 @@ export default {
immediate: true,
},
},
unmounted() {
this.abortOngoingFetchUsage();
},
};
</script>

View File

@ -31,6 +31,7 @@ import { useFileStore } from "@/stores/file";
import url from "@/utils/url";
import { files } from "@/api";
import { StatusError } from "@/api/utils.js";
export default {
name: "file-list",
@ -43,6 +44,7 @@ export default {
},
selected: null,
current: window.location.pathname,
nextAbortController: new AbortController(),
};
},
inject: ["$showError"],
@ -56,7 +58,13 @@ export default {
mounted() {
this.fillOptions(this.req);
},
unmounted() {
this.abortOngoingNext();
},
methods: {
abortOngoingNext() {
this.nextAbortController.abort();
},
fillOptions(req) {
// Sets the current path and resets
// the current items.
@ -94,8 +102,17 @@ export default {
// just clicked in and fill the options with its
// content.
const uri = event.currentTarget.dataset.url;
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
this.abortOngoingNext();
this.nextAbortController = new AbortController();
files
.fetch(uri, this.nextAbortController.signal)
.then(this.fillOptions)
.catch((e) => {
if (e instanceof StatusError && e.is_canceled) {
return;
}
this.$showError(e);
});
},
touchstart(event) {
const url = event.currentTarget.dataset.url;

View File

@ -10,6 +10,7 @@ interface ApiOpts {
method?: ApiMethod;
headers?: object;
body?: any;
signal?: AbortSignal;
}
interface TusSettings {

View File

@ -61,9 +61,7 @@ const route = useRoute();
const { t } = useI18n({});
const clean = (path: string) => {
return path.endsWith("/") ? path.slice(0, -1) : path;
};
let fetchDataController = new AbortController();
const error = ref<StatusError | null>(null);
@ -101,6 +99,7 @@ onUnmounted(() => {
layoutStore.toggleShell();
}
fileStore.updateRequest(null);
fetchDataController.abort();
});
watch(route, (to, from) => {
@ -142,20 +141,21 @@ const fetchData = async () => {
let url = route.path;
if (url === "") url = "/";
if (url[0] !== "/") url = "/" + url;
// Cancel the ongoing request
fetchDataController.abort();
fetchDataController = new AbortController();
try {
const res = await api.fetch(url);
if (clean(res.path) !== clean(`/${[...route.params.path].join("/")}`)) {
throw new Error("Data Mismatch!");
}
const res = await api.fetch(url, fetchDataController.signal);
fileStore.updateRequest(res);
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
layoutStore.loading = false;
} catch (err) {
if (err instanceof StatusError && err.is_canceled) {
return;
}
if (err instanceof Error) {
error.value = err;
}
} finally {
layoutStore.loading = false;
}
};