Fix race condition in NameEditor
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, ref} from "vue";
|
import {ref, onMounted, onUnmounted} from "vue";
|
||||||
import {v7} from "uuid";
|
import {v7} from "uuid";
|
||||||
import {useClient} from "@/plugins/api.js";
|
import {useClient} from "@/plugins/api.js";
|
||||||
|
|
||||||
@@ -18,44 +18,93 @@ const emits = defineEmits([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const name = ref(props.name);
|
const name = ref(props.name);
|
||||||
const isReserved = computed(() => reservationState.value === 'reserved');
|
|
||||||
|
|
||||||
const isOperationPending = ref(false);
|
const isOperationPending = ref(false);
|
||||||
const reservationState = ref(null);
|
const reservationState = ref(null);
|
||||||
const reservationId = ref(v7());
|
|
||||||
|
|
||||||
|
// Use the reservationId from props if provided, otherwise generate a new one
|
||||||
|
const reservationId = ref(props.creatorNameReservationId || v7());
|
||||||
|
|
||||||
|
// Ensure we emit the reservationId on mount if we generated a new one
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.creatorNameReservationId) {
|
||||||
|
emits('update:creatorNameReservationId', reservationId.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request handling
|
||||||
|
let currentController = null;
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
const handleInput = () => {
|
let lastProcessedName = '';
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() =>
|
const cancelCurrentRequest = () => {
|
||||||
checkNameAvailability(),
|
if (currentController) {
|
||||||
200);
|
currentController.abort();
|
||||||
|
currentController = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const client = useClient()
|
const handleInput = () => {
|
||||||
const checkNameAvailability = async () => {
|
clearTimeout(timeout);
|
||||||
if (!name.value || name.value.trim() === "") {
|
timeout = setTimeout(() => {
|
||||||
|
const currentName = name.value;
|
||||||
|
if (currentName === lastProcessedName) {
|
||||||
|
return; // Skip if we've already processed this exact name
|
||||||
|
}
|
||||||
|
checkNameAvailability(currentName);
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = useClient();
|
||||||
|
const checkNameAvailability = async (nameToCheck) => {
|
||||||
|
if (!nameToCheck || nameToCheck.trim() === "") {
|
||||||
reservationState.value = null;
|
reservationState.value = null;
|
||||||
|
lastProcessedName = nameToCheck;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel any ongoing request
|
||||||
|
cancelCurrentRequest();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isOperationPending.value = true;
|
isOperationPending.value = true;
|
||||||
reservationState.value = "loading";
|
reservationState.value = "loading";
|
||||||
|
|
||||||
|
// Create a new request with cancellation token
|
||||||
|
const controller = new AbortController();
|
||||||
|
currentController = controller;
|
||||||
|
|
||||||
await client.post(
|
await client.post(
|
||||||
`/api/creators/@${encodeURIComponent(name.value)}/reserve`,
|
`/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`,
|
||||||
{reservationId: reservationId.value}
|
{ reservationId: reservationId.value },
|
||||||
|
{ signal: controller.signal }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Only process the response if this is still the current request
|
||||||
|
if (currentController === controller) {
|
||||||
reservationState.value = "reserved";
|
reservationState.value = "reserved";
|
||||||
|
lastProcessedName = nameToCheck;
|
||||||
|
emits('update:name', nameToCheck);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reservationState.value = "unavailable"; // Handle API failure case
|
// Only process the error if this is still the current request and it's not an abort error
|
||||||
|
if (currentController && error.name !== 'AbortError') {
|
||||||
|
reservationState.value = "unavailable";
|
||||||
|
lastProcessedName = nameToCheck;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
emits('update:name', name.value);
|
if (currentController) {
|
||||||
emits('update:creatorNameReservationId', reservationId.value);
|
|
||||||
isOperationPending.value = false;
|
isOperationPending.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cleanup on component unmount
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelCurrentRequest();
|
||||||
|
clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user