I want to handle fetch errors in this way:
- If first fetch request fails. Then make 3 more attempts. So 4 attempts to fetch data in total.
- After first attempt show countdown timer. When timer becomes 0 then make attempt to fetch data.
- While fetching data — hide timer and show loader "Fetching data".
- 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)