Home > Mobile >  How to retry failed fetch requests and manage conditions?
How to retry failed fetch requests and manage conditions?

Time:07-05

I want to handle fetch errors in this way:

  1. If first fetch request fails. Then make 3 more attempts. So 4 attempts to fetch data in total.
  2. After first attempt show countdown timer. When timer becomes 0 then make attempt to fetch data.
  3. While fetching data — hide timer and show loader "Fetching data".
  4. After all attempts if fetch fails then display message "Server is down".

The first problem is that my fetch function makes only 3 requests, set error state which renders timer 3 times as well. But it should make 4 request and set state in a way that timer is rendered only 3 times.

The second problem is that its not clear how to manage conditions properly. So that when 3 attempt (4th fetch request) fails then display only message Server is down.

Please help.

What I have is: Fetch function.

async fetchProducts({ commit, dispatch }, attempt = 1) {
  try {
    const response = await fetch("https://fakestoreapi.com/products222");

    await wait(2000);
    if (!response.ok) {
      throw new Error("Something went wrong");
    }
  } catch (e) {
    console.log("Request:", attempt);
    commit("setError", {
      error: {
        isError: true,
        attempt: attempt,
        timerSec: attempt * 2
      }
    });

    if (attempt < 3) {
      await wait(attempt * 3000);
      return dispatch("fetchProducts", attempt   1);
    }
  }
}

Component which renders timer and messages:

<template>
  <div>
    <h1 v-if="!isFetching">Time before attempt: {{ timer }}</h1>
    <div v-else-if="isFetching">
      <h1 >Attempt to fetch {{ attempt }}</h1>
      <h1>Fetching data</h1>
    </div>
    <h2>Server is down</h2>
  </div>
</template>

<script>
export default {
  props: ["timeout", "attempt"],
  data() {
    return {
      timer: null,
      isFetching: false,
      totalAttemtps: 3,
    };
  },

  methods: {
    countDown(sec) {
      this.timer = sec;
      let interval = setInterval(() => {
        this.timer--;
        if (this.timer === 0) {
          clearInterval(interval);
          this.isFetching = true;
          return;
        }
      }, 1000);
    },
  },

  watch: {
    timeout: {
      immediate: true,
      handler() {
        this.isFetching = false;
        this.countDown(this.timeout);
      },
    },
  },
};
</script>

<style>
.title {
  color: red;
}
</style>

I also made codesandbox: https://codesandbox.io/s/peaceful-sinoussi-ozjkq8?file=/src/store/store.js

CodePudding user response:

OK, I think I understand what you want - lots of changes, commented in the code

store.js

import { createStore }
from "vuex";

const wait = async(ms) => {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
};

export default createStore({
    state: {
        error: {
            isError: false,
            attempt: null,
            timerSec: null
        },
        serverIsDown: false // added serverIsDown
    },

    mutations: {
        setError(state, payload) {
            state.error = payload.error;
        },
         // added serverDown
        serverDown(state, payload) {
            state.serverIsDown = payload;
        }
    },

    actions: {
        async fetchProducts({ commit, dispatch }, attempt = 1) {
            try {
                commit("serverDown", false); // set server up
                const response = await fetch("https://fakestoreapi.com/products222");
                await wait(2000);
                if (!response.ok) {
                    throw new Error("Something went wrong");
                }
            } catch (e) {
                console.log("Request:", attempt);
                if (attempt < 4) { // 4, because that's how many attempts you want
                    commit("setError", {
                        error: {
                            isError: true,
                            attempt: attempt,
                            timerSec: attempt * 2
                        }
                    });
                    await wait(attempt * 2000   500);
                    return dispatch("fetchProducts", attempt   1);
                } else { // added to set server down and setError with timerSec = 0 - so server is down message is still displayed
                    commit("serverDown", true);
                    commit("setError", {
                        error: {
                            isError: true,
                            attempt: 0,
                            timerSec: 0
                        }
                    });
                }
            }
        }
    }
});

App.vue (excluding style as that is unchanged)

<template>
    <error-request
        v-if="error.isError"
        :timeout="error.timerSec"
        :attempt="error.attempt"
        :down="serverIsDown"
    ></error-request> <!-- added :down property -->
    <h1 v-else>This should be rendered if there's no errors</h1>
</template>

<script>
import { mapActions, mapState } from "vuex";
import ErrorRequest from "./components/ErrorRequest.vue";

export default {
    components: {
        ErrorRequest,
    },
    computed: {
        ...mapState(["error", "serverIsDown"]), // added serverIsDown
    },
    methods: {
        ...mapActions(["fetchProducts"]),
    },

    mounted() {
        this.fetchProducts()
    },
};
</script>

ErrorRequest.vue

<template>
    <div>
        <h2 v-if="down">Server down</h2> <!-- add v-if="down" -->
        <h1 v-else-if="!isFetching">Time before attempt: {{ timer }}</h1>
        <div v-else-if="isFetching">
            <h1 >Attempt to fetch {{ attempt }}</h1>
            <h1>Fetching data</h1>
        </div>
    </div>
</template>

<script>
export default {
    props: ["timeout", "attempt", "down"], // added "down"
    data() {
        return {
            timer: null,
            isFetching: false,
            totalAttemtps: 3,
        };
    },

    methods: {
        countDown(sec) {
            this.timer = sec;
            if (sec) { // no interval when server is down
                let interval = setInterval(() => {
                    this.timer--;
                    if (this.timer === 0) {
                        clearInterval(interval);
                        this.isFetching = true;
                        return;
                    }
                }, 1000);
            }
        },
    },

    watch: {
        timeout: {
            immediate: true,
            handler() {
                this.isFetching = false;
                this.countDown(this.timeout);
            },
        },
    },
};
</script>

As an alternative - remove all references to down/serverDown/serverIsDown etc in the above code

in store you still have the else that just does

            } else {
                commit("setError", {
                    error: {
                        isError: true,
                        attempt: 0,
                        timerSec: 0 // this serves as server is down
                    }
                });
            }

and in ErrorRequest.vue

<template>
    <div>
        <div v-if="isFetching">
            <h1 >Attempt to fetch {{ attempt }}</h1>
            <h1>Fetching data</h1>
        </div>
        <h1 v-else="timeout">Time before attempt: {{ timer }}</h1>
        <h2 v-else>Server down</h2>
    </div>
</template>

and also make sure to KEEP

if (sec) { // no interval when server is down

That should work (not tested though)

  • Related