My need is to upload a large file (several megabytes or even at least one gigabyte) and allow users to cancel the current upload request.
Unfortunately, when i cancel the request via AbortController, the next requests are in pending status.
Here is my stack :
Scenario 1 (standard scenario) :
Result :
Scenario 2 (where the issue is reproduced) :
Result :
And even if i cancel the API 2, no other request can successfully achieve...
NB : i tried Axios but also XmlHttpRequest and Superagent...
Thanks a lot for your help
I tried all solutions i've found here and in other places...
Here is my code :
<script setup lang="ts">
// Vendor
import { ref, computed } from 'vue'
import axios, { AxiosError, AxiosProgressEvent } from 'axios';
// Stores
import { useApiStore } from 'src/stores/api';
// Components
import QProgressBar from '@components/QProgressBar.vue'
// Personal libs
import { logger } from 'src/shared/lib/logger/logger-manager';
import endpoints from 'src/shared/service/endpoint/endpoints.class';
import { HttpMethodEnum } from '@shared/enum/httpmethod.enum';
import { IApiResponse } from '@shared/interface/api-response.interface';
import { ApiStatusEnum } from '@shared/enum/api-enum';
/**
* Stores
*/
const apiStore = useApiStore();
/**
* Reactive datas
*/
const videoFile = ref<File | undefined>(undefined)
const responseApi1 = ref<IApiResponse<string, string> | undefined>(undefined)
const responseApi2 = ref<IApiResponse<string, string> | undefined>(undefined)
const responseUpload = ref<IApiResponse<string, string> | undefined>(undefined)
const loadingApi1 = ref<boolean>(false)
const loadingApi2 = ref<boolean>(false)
const loadingUpload = ref<boolean>(false)
const timer1 = ref(1)
const timer2 = ref(3)
const abortControllerApi1 = ref<AbortController>()
const abortControllerApi2 = ref<AbortController>()
const abortControllerUpload = ref<AbortController>()
/**
* computed properties
*/
const loaded = computed<number | undefined>(() => apiStore.uploadLoaded);
const total = computed<number | undefined>(() => apiStore.uploadTotal);
/**
* Methods
*/
const onUploadProgress = (progressEvent: AxiosProgressEvent) => {
const apiStore = useApiStore();
apiStore.uploadLoaded = progressEvent.loaded;
apiStore.uploadTotal = progressEvent.total;
};
const baseURL = `${process.env.BACKEND.host ?? ''}${
process.env.BACKEND.port ? ':' + process.env.BACKEND.port : ''
}${process.env.BACKEND.context ?? ''}`;
const buildUrl = (api: string): string => {
return baseURL + api;
};
function onReset() {
videoFile.value = undefined
}
async function callApi1() {
loadingApi1.value = true
responseApi1.value = undefined
const endpoint = endpoints.test.getApi1
const path = buildUrl(endpoint.addParams([timer1.value]).getUrl());
abortControllerApi1.value = new AbortController()
axios({
withCredentials: true,
signal: abortControllerApi1.value?.signal,
responseType: 'json',
method: HttpMethodEnum.GET,
url: path
})
.then((response) => responseApi1.value = response.data as IApiResponse<string, string>)
.catch((error: AxiosError) => logger?.info(error))
.finally(() => loadingApi1.value = false)
}
async function callApi2() {
loadingApi2.value = true
responseApi2.value = undefined
const endpoint = endpoints.test.getApi2
const path = await buildUrl(endpoint.addParams([timer2.value]).getUrl());
abortControllerApi2.value = new AbortController()
axios({
withCredentials: true,
signal: abortControllerApi2.value?.signal,
responseType: 'json',
method: HttpMethodEnum.GET,
url: path
})
.then((response) => responseApi2.value = response.data as IApiResponse<string, string>)
.catch((error: AxiosError) => logger?.info(error))
.finally(() => loadingApi2.value = false)
}
async function doPostVideo() {
if (videoFile.value != null) {
loadingUpload.value = true
responseUpload.value = undefined
const endpoint = endpoints.test.postVideo
const path = await buildUrl(endpoint.getUrl());
const postData = new FormData();
postData.append('videoFile', videoFile.value)
abortControllerUpload.value = new AbortController()
axios({
withCredentials: true,
signal: abortControllerUpload.value?.signal,
// signal: AbortSignal.timeout(2000),
responseType: 'json',
method: HttpMethodEnum.POST,
url: path,
data: postData,
onUploadProgress: onUploadProgress
})
.then((response) => {
if (response.data?.returnCode != null) {
responseUpload.value = response.data as IApiResponse<string, string>
} else {
logger?.info(response)
}
})
.catch((error: AxiosError) => {
if (axios.isCancel(error)) {
responseUpload.value = {
returnCode: 'CANCELLED',
returnStatus: ApiStatusEnum.ERROR,
returnError: 'Request cancelled by user'
}
} else {
logger?.error(error)
}
})
.finally(() => {
loadingUpload.value = false
resetProgressBar()
})
}
}
function cancelApi1() {
abortControllerApi1.value?.abort()
loadingApi1.value = false
}
function cancelApi2() {
abortControllerApi2.value?.abort()
loadingApi2.value = false
}
function cancelUpload() {
abortControllerUpload.value?.abort()
loadingUpload.value = false
resetProgressBar()
}
function resetProgressBar() {
apiStore.uploadLoaded = undefined;
apiStore.uploadTotal = undefined;
}
</script>
And here are the screenshots :
I finally found a solution : TUS (The Upload Server).
I hope it will save your life !