Avoid potential issues caused by cancelled asynchronous requests affecting the application's status by using the watchEffect() function

Imagine a scenario where a component receives the ID of a resource through a prop called resourceId. This component is responsible for fetching the corresponding resource from an API and displaying it, while also managing loading and error states (similar to the pattern outlined here).

The function that retrieves the resource from the external API includes an abort function which, when invoked, immediately rejects the request. If the value of resourceId changes during an ongoing request, the existing request should be aborted in favor of initiating a new one. Just to provide some context, this implementation involves using fetch() and AbortController.

Utilizing Vue 3 with the composition API, I developed an implementation that resembles the following:

const loading = ref(false);
const data = ref(null);
const error = ref(null);

watchEffect(async (onCancel) => {
    loading.value = true;
    data.value = error.value = null;

    const { response, abort } = fetchResourceFromApi(props.resourceId);
    onCancel(abort);

    try {
        data.value = await (await response).json();
    } catch (e) {
        error.value = e;
    } finally {
        loading.value = false;
    }
});
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ data }}</div>

Although this approach generally works well, issues arise when dealing with cancellations. If a change in resourceId occurs before the completion of the last API request, the following sequence of events takes place:

  1. abort() is triggered
  2. The watchEffect callback is executed, setting values for loading, error, and data
  3. The catch and finally blocks from the initial request run, affecting loading and error
  4. The second API request finishes and updates loading and data

This leads to an unexpected state where loading is set to false while the second request is still ongoing, error contains the exception raised by aborting the first request, and data holds the value from the second request.

Are there any recommended design patterns or solutions to address this issue?

Answer №1

if the sequence of events unfolds as you've outlined:

  1. start with watchEffect
  2. abort
  3. move to the second watchEffect
  4. deal with catch and finally from the initial watchEffect

You can maintain a counter that tracks the current ongoing request (effectively acting as a request id or version, as some frameworks may refer to it). Increment the counter each time watchEffect is triggered and take note of the current value. Within your catch and finally block, verify if the current counter matches the initial counter value. If they don't match, it indicates handling outdated request errors, allowing you to skip error modification and loading status updates.

const {ref, watchEffect} = Vue;

const App = {
  setup() {
    const resourceId = ref(0);
    const loading = ref(false);
    const data = ref(null);
    const error = ref(null);

    // simulate an asynchronous API fetch method that returns a result after 3 seconds.
    const fetchFor = (id) => {
      let abort;
      const response = new Promise((resolve, reject) => {
        abort = () => reject(`request ${id} aborted`);
        setTimeout(() => resolve({msg: "result for " + id}), 3000);
      });
      return {response, abort};
    };


    let version = 0;

    watchEffect(async (onCancel) => {
      version ++;
      const currentVersion = version;
      
      loading.value = true;
      data.value = error.value = null;
      const { response, abort } = fetchFor(resourceId.value);
      onCancel(abort);
      try {
        data.value = await response;
      } catch (e) {
        if (currentVersion === version) {
          error.value = e;
        }
        
      } finally {
        if (currentVersion === version) {
          loading.value = false;
        }
      }
    });

    return {resourceId, loading, data, error};
  }
};
   
const app = Vue.createApp(App);
app.mount("#app");
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<div id="app">
 <button @click="resourceId++">Request</button>
 <div> last request is {{resourceId}} </div>
 <div> loading = {{loading}} </div>
 <div> data = {{data?.msg}} </div>
 <div> error = {{error}} </div>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Utilize the on() method to bind a click event to dynamically generated elements received

After reading several discussions on using on() to link events to dynamically generated HTML elements, I decided to create an example (FIDDLE) where the click event is bound to elements div.clicktitle fetched via AJAX. These div elements contain data attri ...

Should you include the dollar sign in a Vue HTML variable or not?

I’m a bit confused about whether or not I should include $ when using a Vue HTML variable: new Vue({ data: { a: "myData" } }); Do I need to use: <h1>My value is {{ a }}</h1> or <h1>My value is {{ $a }}</h1> What ...

Ways to conceal an animated gif once it has been downloaded?

Is it possible to have an animated gif image vanish once server-side Java code runs and the client receives an HTTP Response from the webserver without relying on Ajax? I am currently utilizing the following Struts2 submit button: <s:submit value="sho ...

Utilizing a promise instead of making a jQuery ajax request

A scenario I am facing involves a function that is set to execute jquery.ajax and return it as a promise for future processing. However, in certain cases, the function possesses enough information to proceed synchronously without triggering the ajax call. ...

Verification response parameter not received from Google Authentication Express

I've been working on implementing a "sign in with Google" feature for my localhost website. Despite receiving the POST request, I noticed that it lacks the credential parameter. I'm unsure if this issue lies within the code or the API configurati ...

Adaptive Container with Images that are not stretched to full width

Is there a way to achieve the same effect as seen in images 2 and 3 here: Although these images already have their own "padding," I'm curious if it can be replicated using just jQuery and CSS? I would appreciate any help or insights on this. Thank y ...

Reviewing user input for any inappropriate characters using jQuery's functionality

When a username is inputted into the input box, I want to make sure that only valid characters are accepted. The following code shows what I have so far; but what should I replace "SOMETHING" with in the regular expression? var numbers = new RegExp( ...

Creating a personalized scrollball feature within a fancybox

Within my fancybox, I have a section with images that require a scroll bar. I currently have a scroll bar in place, but I am interested in implementing an anti-scroll (custom scrollbar) instead. I came across one option at https://github.com/Automattic/an ...

Exploring effective testing approaches for C++ plugins in Node.js

When working on Node JS, I have experience creating native C++ modules. However, my testing approach typically involves writing tests for these modules in Javascript. I am curious if this is an effective test strategy or if there are more optimal ways to ...

"Designing with Vue.js for a seamless and adaptive user experience

I'm looking to customize the display of my data in Vuejs by arranging each property vertically. Instead of appearing on the same line, I want every item in conversationHistory to be displayed vertically. How can I achieve this in Vuejs? Can anyone off ...

Forward to a SubDomain

Utilizing Yellowtree's GEOIP-Detect plugin, I attempted to implement a location-based redirection system for visitors. Unfortunately, I encountered issues with the code execution. The process initially involves extracting the user's IP address an ...

Retrieving External JSON Data from a Server with Firefox

I've been developing a static local HTML5 charting application that retrieves data from a remote server for output. The code works perfectly in Google Chrome, as shown below, but I'm encountering difficulties getting it to function in Firefox. & ...

Initiate monitoring for child component modifications

I'm looking to disable 'changeDetection' for the parent component while enabling it for the child component. Can you provide an example of how this can be achieved? The parent component contains static data, meaning change detection is not ...

Comparing JSON Objects in Javascript

I'm in the process of developing a web application that retrieves data from a server and displays it to the user. The script pulls data from the server every 10 seconds, and if there's any change in the data, it alerts the user. Currently, the co ...

Unable to redirect page in Codeigniter after submitting button

I am facing an issue with inserting and updating data in my database using Ajax to my controller. Despite the data being inserted and updated accurately after clicking the button, the changes are not reflected on my view page until I manually refresh it. A ...

Creating HTML form input fields for reading and writing an XML file

Currently working on constructing an HTML form containing input fields to read and modify data from an XML file. The initial task involves loading values into the input fields upon page load, but unfortunately, it is not functioning as expected. < ...

Tips for adding React components to an array with the help of backticks

Currently, I am attempting to populate an array with icons by extracting the name from data and concatenating "<" and "/>" around it in order to convert it into an Mui Icon. Despite renaming the imported icons to match the names in the data, when I ...

Tips for avoiding flickering in a background image when it is being changed

Utilizing JavaScript, I am setting a repeated background image from a canvas to a div in the following way: var img_canvas = document.createElement('canvas'); img_canvas.width = 16; img_canvas.height = 16; img_canvas.getContext('2d' ...

Pass an array using AJAX to my Python function within a Django framework

I am attempting to pass an array to my python function within views.py, but I am encountering issues. It consistently crashes with a keyError because it does not recognize the data from js. Code: Python function in views.py: def cargar_datos_csv(request ...

Improved AJAX Dependency

A few days ago, a question was posted with messy code and other issues due to my lack of experience (please forgive the form handling as well). However, I have made some improvements and added context. The main problem now lies in the second AJAX call. Ch ...