axios cancellation caught inside of then() instead of catch() - vue.js

I making a multi-upload file form.
Upon user cancellation, once the corresponding axios call get cancelled using cancel(), I having a weird behaviour. My axios call get caught inside the then() whereas it should be caught inside of catch(). The response inside of then() returns undefined.
I am having a hard time figuring if I did something wrong on the front-end part, I think my call is may be missing some headers or maybe it's on the backend part ?
const payload = { file, objectId: articleId, contentType: 'article' };
const source = axios.CancelToken.source();
// callback to execute at progression
const onUploadProgress = (event) => {
const percentage = Math.round((100 * event.loaded) / event.total);
this.handleFileUploadProgression(file, {
percentage,
status: 'pending',
cancelSource: source,
});
};
attachmentService
.create(payload, { onUploadProgress, cancelToken: source.token })
.then((response) => {
// cancelation response ends up here with a `undefined` response content
})
.catch((error) => {
console.log(error);
// canceled request do not reads as errors down here
if (axios.isCancel(error)) {
console.log('axios request cancelled', error);
}
});
the service itself is defined below
export const attachmentService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api
.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
})
.then((response) => {
console.log(response, 'cancelled request answered here as `undefined`');
return response.data;
})
.catch((error) => {
// not caught here (earlier)
return error.data;
});
},
};
cancellation is called upon a file object doing
file.cancelSource.cancel('Request was cancelled by the user');

As suggested by #estus-flask in a comment, the issue is that I was catching the error inside of the service (too early). Thank you!
export const articleService = {
create(payload, requestOptions) {
// FormData cannot be decamelized inside an interceptor so it's done before, here.
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) =>
formData.append(decamelize(key), value),
);
return api.post(resource, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
...requestOptions,
});
},
};

Related

How to properly await Nuxt calls with async/await or .then

Im trying to fetch an API using chaining with .then but I don't figure it out I try like:
async fetch() {
let path = this.$nuxt.context.route.path
this.response = await axios.get(
`/api${path}`,
{
headers: {
'X-AUTH-TOKEN': process.env.SECURE_TOKEN,
'Content-Type': 'application/json'
}
}
).then((res) => {
this.results = res.data.content.slice(0,40);
return results();
})
.then((res) => {
this.results2 = res.data.content.slice(20,40);
return results2();
})
},
For my API data load: when results is finish /results2 start to load, for using it with $fetchState.pending
What will be the best way of doing it? I'm trying to adapt the answer from here but no success so far.
This kind of code should be working fine
<script>
export default {
async fetch() {
this.response = await axios
.get(`/api${this.$route.path}`, { // faster than this.$nuxt.context.route.path
headers: {
'X-AUTH-TOKEN': process.env.SECURE_TOKEN,
'Content-Type': 'application/json',
},
})
.then((res) => { // ❌ this is useless because you're already using await above
const results = res.data.content.slice(0, 40)
return results()
})
.then((res) => { // ❌ this is useless because `slice` is NOT async
const results2 = res.data.content.slice(20, 40)
return results2()
})
},
}
</script>
Otherwise, I can also recommend a better approach overall, using async/await and not mixing it with .then at all, like this
<script>
export default {
async fetch() {
const response = await axios.get(
`/api${this.$route.path}`,
{
headers: {
'X-AUTH-TOKEN': process.env.SECURE_TOKEN,
'Content-Type': 'application/json',
},
}
)
const results = response.data.content.slice(0, 40)
const results2 = results.data.content.slice(20, 40)
},
}
</script>
PS: note that some things are not async, hence do not need await (or .then at all).
It can even be shorten to the following
<script>
export default {
async fetch() {
const response = await this.$axios.$get( // 👈🏻 using the shortcut $get
`/api${this.$route.path}`,
{
headers: {
'X-AUTH-TOKEN': process.env.SECURE_TOKEN,
'Content-Type': 'application/json',
},
}
)
const results = response.content.slice(0, 40) // 👈🏻 no need for `.data` here
const results2 = results.content.slice(20, 40) // 👈🏻 same here
},
}
</script>
Thanks to the shortcuts available with the axios module that you should be using anyway.
As of why you should use async/await, it's just more lisible and available everywhere already (IE is dead). Here is some more info about the whole async/await thing: https://javascript.info/async-await
Far prettier than this kind of syntax (also named callback hell)

RN "TypeError: Network request failed" - production - random

I know some questions about the subject has been opened here and there, but my issue is different :
all the other ones appear in dev mode, in my case it's in production,
a very big percentage of requests pass, a few of them is TypeError: Network request failed - but sometimes for critical requests
it's random, not always the same request. Sometimes it passes, sometimes not.
it appears to three on my projects, one is on AWS the other one on Clever-Cloud, both are projects between 1000 and 5000 users, servers are quite too big for what they do - I think I removed the risk of a server fault. Even if... I can reproduce locally when I don't start the api locally. So it's like the api is not responding, but as I said, I don't think so.
I have no clue where to dig anymore...
I can give you my API.js service file, maybe you'll find what's wrong ?
import URI from 'urijs';
import { Platform } from 'react-native';
import NetInfo from '#react-native-community/netinfo';
import { getUserToken, wipeData } from '../utils/data';
import { SCHEME, MW_API_HOST } from '../config';
import deviceInfoModule from 'react-native-device-info';
import { capture } from '../utils/sentry';
const unauthorisedHandler = (navigation) => {
wipeData();
navigation.reset({ index: 0, routes: [{ name: 'Auth' }] });
};
const checkNetwork = async (test = false) => {
const isConnected = await NetInfo.fetch().then((state) => state.isConnected);
if (!isConnected || test) {
await new Promise((res) => setTimeout(res, 1500));
return false;
}
return true;
};
class ApiService {
host = MW_API_HOST;
scheme = SCHEME;
getUrl = (path, query) => {
return new URI().host(this.host).scheme(this.scheme).path(path).setSearch(query).toString();
};
execute = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
try {
const config = {
method,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
appversion: deviceInfoModule.getBuildNumber(),
appdevice: Platform.OS,
currentroute: this.navigation?.getCurrentRoute?.()?.name,
...headers,
},
body: body ? JSON.stringify(body) : null,
};
const url = this.getUrl(path, query);
console.log('url: ', url);
const canFetch = await checkNetwork();
if (!canFetch) return;
let response;
// To try to avoid mysterious `TypeError: Network request failed` error
// that throws an error directly
// we try catch and try one more time.
try {
response = await fetch(url, config);
} catch (e) {
if (e?.toString().includes('Network request failed')) {
// try again
await new Promise((res) => setTimeout(res, 250));
console.log('try again because Network request failed');
response = await fetch(url, config);
} else {
throw e;
}
}
if (!response.ok) {
if (response.status === 401) {
const token = await getUserToken();
if (token) unauthorisedHandler(API.navigation);
return response;
}
}
if (response.json) return await response.json();
return response;
} catch (e) {
capture(e, { extra: { method, path, query, headers, body } });
return { ok: false, error: "Sorry, an error occured, technical team has been warned." };
}
};
executeWithToken = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
const token = await getUserToken();
if (token) headers.Authorization = token;
return this.execute({ method, path, query, headers, body });
};
get = async (args) => this.executeWithToken({ method: 'GET', ...args });
post = async (args) => this.executeWithToken({ method: 'POST', ...args });
put = async (args) => this.executeWithToken({ method: 'PUT', ...args });
delete = async (args) => this.executeWithToken({ method: 'DELETE', ...args });
}
const API = new ApiService();
export default API;
Talking with experts here and there, it seems that it's normal : internet network is not 100% reliable, so sometimes, request fail, for a reason that we can't anticipate (tunnel, whatever).
I ended up using fetch-retry and I still have a few of those, but much less !

How can I console log axios response outside the request

async login(context, payload) {
const response = await axios
.post(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyCQ6w2jvJVNrOwON4-KnEOV1kH-ckEDokg',
{
email: payload.email,
password: payload.password,
returnSecuredToken: true
},
{
Headers: {
'Content-Type': 'application/json'
}
}
)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
console.log(error.response);
});
console.log(response);
context.commit('setUser', {
token: response.data.idToken,
userId: response.data.userId,
tokenExpiration: response.data.expiresIn
});
Hello, maybe it's a dumb question but how can I console.log my response there ? I tried to stock my response in a const but it's the same issue, the console log and the commit execute before the await async and I can't use any data that return from the axios response, thanks if you take the time to help me.
You don't need then function in this case. With await word is enough
The problem is your intermediate .then returns nothing, so the return value of await axios.post() resolves to undefined.
You could either remove the unnecessary .then:
const response = await axios.post(/*...*/);
console.log(response);
...or return response in .then:
const response = await axios.post(/*...*/)
.then(response => {
console.log(response);
return response;
});

Cancel an axios request, when not having the fetch in useEffect

I am trying to implement the cancel request function for my axios post. I created own js files for the functions and I am importing them into my screens. Here is an example
App.js
cancelToken.current = axios.CancelToken.source()
async function getFeed() {
let x = await service.getUserFeed(user_id, cancelToken);
setData(x); }
getFeed();
return () => {
cancelToken.cancel();
}
},[user_id]);
service.js:
getUserFeed: async (token, user_id, source) => {
let x;
await axios.post(Default.apiEndpoint + 'feed',
{
},
{
cancelToken: source.token,
headers: {
'Accept': "application/json",
'Content-Type': "application/json",
'user_id': user_id,
}
}).then(response => {
x = response.data;
}).catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
else {
x = error.response.status;
}
});
return x;
},
If I am calling the request in the hook itself it is working. So I am not even sure, if it is possible, because the request is not triggered, when the user leaves the page. So I would need to cancel the request in the service.js itself (I guess). Did anyone implement that already and can help me here?
Thanks

Doing a Timeout Error with Fetch - React Native

I have a user login function that is working. But, I want to incorporate a time out error for the fetch. Is there a way to set up a timer for 5 seconds or so that would stop trying to fetch after such a time? Otherwise, I just get a red screen after a while saying network error.
_userLogin() {
var value = this.refs.form.getValue();
if (value) {
// if validation fails, value will be null
if (!this.validateEmail(value.email)) {
// eslint-disable-next-line no-undef
Alert.alert('Enter a valid email');
} else {
fetch('http://51.64.34.134:5000/api/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
timeout: 5000,
body: JSON.stringify({
username: value.email,
password: value.password,
}),
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == 'success') {
this._onValueChange(STORAGE_KEY, responseData.data.token);
Alert.alert('Login Success!');
this.props.navigator.push({name: 'StartScreen'});
} else if (responseData.status == 'error') {
Alert.alert('Login Error', responseData.message);
}
})
.done();
}
}
}
I have made a ES6 function that wraps ES fetch into a promise, here it is:
export async function fetchWithTimeout(url, options, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
Here is how to use it:
const requestInfo = {
method,
headers,
body,
};
const url = 'http://yoururl.edu.br'
let data = await fetchWithTimeout(url, requestInfo, 3000);
// Wrapper function for fetch
const fetchSomething = async () => {
let controller = new AbortController()
setTimeout(() => controller.abort(), 3000); // abort after 3 seconds
const resp = await fetch('some url', {signal: controller.signal});
const json = await resp.json();
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
return json;
}
// usage
try {
let jsonResp = await fetchSomthing();
console.log(jsonResp);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Network Error');
} else {
console.log(error.message);
}
}
I think using AbortController is the recommended way to abort a fetch call. The code snippet above handles the following scenarios:
If network is good but HTTP returns an error status, the message "HTTP error! ..." will be logged.
If network is down, setTimeout would trigger the AbortController to abort fetch after three seconds. The message "Network Error" will be logged.
If network is good and HTTP response is good, the response JSON will be logged.
The documentation for using AbortController to abort fetch is here.
There is no standard way of handling this as a timeout option isn't defined in the official spec yet. There is an abort defined which you can use in conjunction with your own timeout and Promises. For example as seen here and here. I've copied the example code, but haven't tested it myself yet.
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, fetch('/hello')).then(function(response) {
// process response
}).catch(function(error) {
// might be a timeout error
})
Another option would be to modify the fetch.js module yourself to add a timeout that calls abort as seen here.
This is what I did to go around it:
(This is the "generic" function I use to make all calls on my app)
I created a timeout function, that will be triggered unless it is cleared before, then I clear this timeout on server response
const doFetch = (url, callback, data) => {
//... creating config obj here (not relevant for this answer)
var wasServerTimeout = false;
var timeout = setTimeout(() => {
wasServerTimeout = true;
alert('Time Out');
}, 3000);
fetch(HOST + url, config)
.then((response) => {
timeout && clearTimeout(timeout); //If everything is ok, clear the timeout
if (!wasServerTimeout) {
return response.json();
}
})
.then((response) => {
callback && callback(response.data || response);
})
.catch((err) => {
//If something goes wrong, clear the timeout
timeout && clearTimeout(timeout);
if (!wasServerTimeout) {
//Error logic here
}
});
};
I solved this problem by using a race between 2 promises, written as a wrapper around fetch. In my case I expect the request to return json so also added that. Maybe there is a better solution, but this works correctly for me!
The wrapper returns a promise which will resolve as long as there are no code errors.
You can check the result.status for 'success' and read json data from result.data. In case of error you can read the exact error in result.data, and display it or log it somewhere. This way you always know what went wrong!
var yourFetchWrapperFunction = function (
method,
url,
headers,
body,
timeout = 5000,
) {
var timeoutPromise = new Promise(function (resolve, reject) {
setTimeout(resolve, timeout, {
status: 'error',
code: 666,
data:
'Verbinding met de cloud kon niet tot stand gebracht worden: Timeout.',
});
});
return Promise.race([
timeoutPromise,
fetch(connectionType + '://' + url, {
method: method,
headers: headers,
body: body,
}),
])
.then(
(result) => {
var Status = result.status;
return result
.json()
.then(
function (data) {
if (Status === 200 || Status === 0) {
return {status: 'success', code: Status, data: data};
} else {
return {
status: 'error',
code: Status,
data: 'Error (' + data.status_code + '): ' + data.message,
};
}
},
function (response) {
return {
status: 'error',
code: Status,
data: 'json promise failed' + response,
};
},
)
.catch((error) => {
return {status: 'error', code: 666, data: 'no json response'};
});
},
function (error) {
return {status: 'error', code: 666, data: 'connection timed out'};
},
)
.catch((error) => {
return {status: 'error', code: 666, data: 'connection timed out'};
});
};
let controller = new AbortController()
setTimeout( () => {
controller.abort()
}, 10000); // 10,000 means 10 seconds
return fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(param),
signal: controller.signal
})
I may be late but i made a code which is 100% working to timeout an API request using fetch.
fetch_timeout(url, options) {
let timeout = 1000;
let timeout_err = {
ok: false,
status: 408,
};
return new Promise(function (resolve, reject) {
fetch(url, options)
.then(resolve, reject)
.catch(() => {
alert('timeout.');
});
setTimeout(reject.bind(null, timeout_err), timeout);
});
}
You just need to pass the api-endpoint to the url and body to the options parameter.