Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

231
Visualizações
How to prevent aborted async requests from corrupting state with watchEffect()

Say we have a component that receives the ID of a resource through a prop, resourceId. The component should fetch the corresponding resource from an API and display it, while also handling loading/error states (following a pattern similar to this one).

The function that fetches the resource from the external API also returns an abort function which, if called, causes the request to immediately reject. When resourceId changes, any in-flight requests should be aborted in favor of a new request. For what it's worth, I'm using fetch() and AbortController for this.

Using Vue 3 with the composition API, I came up with an implementation that looks something like this:

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>

This works fine under most circumstances, but breaks whenever the cancellation comes in to play. If resourceId changes before the last API request has finished, the following order of events happens:

  1. abort() gets called
  2. watchEffect callback gets called, setting loading, error, and data
  3. catch and finally blocks from original request are called, setting loading and error
  4. Second API request completes and sets loading and data

This results in an unexpected state where loading is set to false while the second request is in flight, error contains the exception raised by aborting the first request, and data contains the value from the second request.

Are there any design patterns or workarounds that can deal with this problem?

about 4 years ago · Juan Pablo Isaza
1 Respostas
Responde à pergunta

0

if the sequence of events is as you described:

  1. first watchEffect
  2. abort
  3. second watchEffect
  4. catch and finally of the first watchEffect

Then you can keep a counter and use it to track what the current in flight request is (effectively it is a request id, or version as some libraries call it). Every time watchEffect is called, increase the counter and take a snapshot of the current value. Check in your catch and finally block if the present counter is the same as the counter value you started with. In case they mismatch, it means you are handling stale request errors so you can skip the modification of error and loading status.

const {ref, watchEffect} = Vue;

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

    // fake an api fetch method to asynchronously return 
    // 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>

about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda