Skip to content

How to Abort API Requests and Handle Errors in Vue.js with VueUse

Introduction

When working with asynchronous operations in Vue applications, it's crucial to manage requests efficiently, especially when dealing with potentially long-running or unnecessary requests. In this article, we'll explore how to abort requests using VueUse and handle errors thrown by these aborted requests.

Problem

In modern web applications, it's common to make multiple API calls to fetch data. However, there are scenarios where we might want to cancel ongoing requests, such as when a user navigates away from a page or when a new request supersedes an existing one. Additionally, we need to handle errors that may occur when aborting these requests to ensure a smooth user experience.

Context

VueUse provides a powerful useFetch composable that allows us to make HTTP requests with built-in abort functionality. Let's look at how we can leverage this feature:

typescript
const { abort, canAbort } = useFetch(url);

setTimeout(() => {
  if (canAbort.value) abort();
}, 100);

In this example, we can abort a request using the abort function provided by useFetch. The canAbort property indicates whether the request can be aborted.

We can also set an automatic timeout for aborting requests:

typescript
const { data } = useFetch(url, { timeout: 100 });

This will automatically abort the request after 100 milliseconds.

Solution

Now, let's implement a solution that demonstrates how to abort requests and handle errors in a Vue component. We'll create a component that fetches messages and allows the user to cancel the request by clicking a button multiple times.

vue
<template>
  <div>
    <button @click="getMessages">Fetch Messages</button>
    <div v-if="loading">Loading...</div>

    <ul v-else>
      <li v-for="message in messages" :key="message.id">
        {{ message.text }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useFetch } from '@vueuse/core';
import { defineStore } from 'pinia';

interface Message {
  id: number;
  text: string;
}

const useStore = defineStore('main', {
  state: () => ({
    cancellableRequests: new Map<string, { abort: () => void }>(),
  }),
});

const store = useStore();

const messages = ref<Message[]>([]);
const loading = ref(false);

const getMessages = async (): Message[] => {
  try {
    loading.value = true;

    const url = '/api/messages';
    const token = 'getMessages';

    if (store.cancellableRequests.has(token)) {
      store.cancellableRequests.get(token)?.abort();
      store.cancellableRequests.delete(token);
    }

    const { execute, abort, data, error } = useFetch(url, {
      immediate: false,
    })
      .get()
      .json<{ messages: Message[] }>();

    store.cancellableRequests.set(token, { abort });

    await execute();

    if (error.value) {
      if error.value instanceof Error && error.value.name === 'AbortError') {
        store.cancellableRquests.delete(token);
        return []
      }

      throw new Error('Something went wrong. Try again later.')
    }

    return data.value.messages;
  } finally {
    loading.value = false;
  }
};
</script>

In this example, we've created a Vue component that demonstrates how to abort requests and handle errors:

  1. We define a Pinia store to manage cancellable requests.
  2. The getMessages function is responsible for fetching messages and handling the request lifecycle.
  3. Before making a new request, we check if there's an existing request with the same token and abort it if it exists.
  4. We use useFetch from VueUse to make the API call.
  5. We store the abort function in our Pinia store, allowing us to cancel the request later if needed.
  6. We handle errors, including the AbortError, which is thrown when a request is cancelled.
  7. We return the fetched messages if successful, or an empty array if the request was aborted.
  8. Finally, we clean up by removing the request from our store and setting the loading state to false.

This implementation allows for efficient management of API calls, preventing unnecessary network requests and improving the overall user experience.

Conclusion

By implementing this solution, we've successfully addressed the initial question of how to abort requests using VueUse and handle errors thrown by them. This approach allows for efficient management of API calls, preventing unnecessary network requests and improving the overall user experience.