browseraxiosrequestuploadcancellation

Cancelling an upload request (using xhr, axios or superagent) blocks other following requests


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 :

01 - Success requests

02 - Uploading file

03 - upload cancelled

04 - executing a new request

05 - timing of success upload

06 - timing of cancelled upload request

07 - timing of the new request in pending status


Solution

  • I finally found a solution : TUS (The Upload Server).

    I hope it will save your life !