How to show toast with information just once in project if axios interceptor get from several requests 401 status ?
const responseInterceptor = axios.interceptors.response.use(
response => response,
async (error) => {
const {config: originalRequest, response: {status}} = error
if (status === 401 || status === 403) {
dispatch(userActions.checkError({status, statusText: error.response['statusText']}))
}
return Promise.reject(error)
}
)
Right now it works like - two requests got 401 status and Toast present twice 401 statusText - I'd like to show it just once - How can I do this ?
Related
I made the below utility to handle the web requests for me:
import configureStore from '../configureStore';
import { logout } from '../containers/App/actions';
const store = configureStore({});
/**
* Parses the JSON returned by a network request
*
* #param {object} response A response from a network request
*
* #return {object} The parsed JSON from the request
*/
function parseJSON(response) {
if (
response.status === 204 ||
response.status === 205 ||
parseInt(response.headers.get('content-length')) === 0
) {
return null;
}
return response.json();
}
/**
* Checks if a network request came back fine, and throws an error if not
*
* #param {object} response A response from a network request
*
* #return {object|undefined} Returns either the response, or throws an error
*/
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise
*
* #param {string} url The URL we want to request
* #param {object} [options] The options we want to pass to "fetch"
*
* #return {object} The response data
*/
export default function request(url, options) {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Request-Headers': 'Content-Type, Authorization'
};
const token = localStorage.getItem('token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const newOptions = {
...options,
mode: 'cors',
headers
};
// FIXME
// properly handle `net::ERR_CONNECTION_REFUSED`
// currently, we return `cannot read properties of undefined (reading 'status')`
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
.catch(err => {
// check for 401 here and throw an action to clean the store and the logout.
if (err.response.status === 401) {
store.dispatch(logout);
}
throw err;
});
}
This is working except when a network error happens. Since fetch does not throw on errors, I need to handle this case myself.
Only, it is NOT working. I tried adding if (!response.ok) to the checkStatus. It is however also not working, since the browser throws a CORS error.
How do I handle this, so my request() throws Failed to fetch instead of cannot read properties of undefined (reading 'status')?
Since fetch does not throw on error
Fetch does throw on error:
A fetch() promise only rejects when a network error is encountered
https://developer.mozilla.org/en-US/docs/Web/API/fetch
It won't tell you specifically that the network is down, but it will throw an error.
Your error is likely occurring here:
if (err.response.status === 401) {
That's because fetch is throwing an actual Error instance when the network is down, thus response is null, so you can't access status on null. So if response doesn't exist on err, just throw it:
return fetch(url, newOptions)
.then(checkStatus)
.then(parseJSON)
.catch(err => {
// check for 401 here and throw an action to clean the store and the logout.
if (err != null && 'response' in err && err.response.status === 401) {
store.dispatch(logout);
}
throw err;
});
fetch() does throw on network errors. The only error it doesn't throw is HTTP errors.
ended-up with:
async function parseJSON(response) {
if (
response.status === 204 ||
response.status === 205 ||
parseInt(response.headers.get('content-length')) === 0
) {
return null;
}
return await response.json();
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
export default async function request(url, options) {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Request-Headers': 'Content-Type, Authorization'
};
const token = localStorage.getItem('token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const newOptions = {
...options,
mode: 'cors',
headers
};
try {
const result = await fetch(url, newOptions);
return await parseJSON(checkStatus(result));
} catch (err) {
// check for 401 here and throw an action to clean the store and the logout.
if (err != null && 'response' in err && err.response.status === 401) {
store.dispatch(logout);
}
throw err;
}
}
So I'm developing a c# API that returns some status codes, and this is my function on controller:
public IHttpActionResult validateToken(HttpRequestMessage request, string token)
{
bool result = AuthUser.validateToken(token);
if (!result)
{
return BadRequest();
}
else
{
return Ok();
}
}
Then I have my axios to make the requests:
this.$axios.get("validateResetToken/" + token)
.then((response) => {
console.log(response)
})
.catch((error) => console.log(error));
And when the API returns the 200, Ok(), the response gives me a object:
But when the return is anything than Ok [ 200 ], the response is always undifined and I cant figure it out why.
Why cant I have always a response, to get the status code, in this case 200 or 400.
I have the following React Native client code:
confirmMatchRecord(userId, matchedUserData['userId'], matchRecordData['matchRecord']['matchWinner'], matchRecordData['matchType'], currentUserRating, matchedUserRating, matchData['_id'], matchRecordData['matchRecord']['_id'], airbnbRatingValue, true, new Date())
.then((results) => {
// Do stuff
})
.catch((error) => {
Alert.alert('Error', 'There was an issue with confirming the record. Please check your connection and/or try again later.');
});
And the following code in my confirmMatchRecord function:
export async function confirmMatchRecord(userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp) {
console.log('Attempting to record match');
info = { userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp }
const firebaseIdToken = await AsyncStorage.getItem('#firebaseIdToken')
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + firebaseIdToken },
body: JSON.stringify(info)
};
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (response['Status']==='Failure') {
// throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(400);
} else if (response['Status']==='Success') {
const data = await response.json()
return data
}
}
Server code:
router.post('/confirmmatchrecord', async (req, res) => {
// Do a lot of stuff
if (response==='Success') {
return res.status(200).json({'Status': 'Success'})
} else {
return res.status(400).json({'Status': 'Failure'})
console.log('Match record was not confirmed successfully');
}
When response['Status']==='Failure (sent by server) it throws an error 400 as you can see, I was hoping to trigger the .catch in the client code then. But that does not happen, because the client code continues to run on the .then part.
How should I do this instead? Not sure if using .catch here is even correct or if I should do this type of work another way.
You seem to be aware of the bit of a footgun in the fetch API (I write about it here) where fetch only rejects its promise on network errors, not HTTP errors, but your check is incorrect in a couple of ways:
It's status, not Status (capitalization matters), and
It's the HTTP code (400 for instance), not a string
The Response object provides a convenient ok flag that's true for any successful response and false otherwise, so:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
const data = await response.json();
return data;
In a comment you've said:
My response['Status'] was checking for a custom server message I had sent (res.status(400).json({'Status': 'Failure'}), I updated the post with it. Not sure why it didn't catch that
Ah! Okay. The reason it didn't catch it is that you're looking for it on the Response object, but your JSON is in the response body.
I suspect you don't want to use your own Status anymore since you know about response.ok and response.status now, but if you ever do want to include your own information in an error response as JSON, you can do that. You'd do it like this:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
const data = await response.json(); // Expects JSON in *both* the success response and the error response
if (data.Status === "Failure") {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
return data;
But I'd stick with just the built-in ok and status for pure success/failure information. This could be handy if you wanted to provide more details of the failure, though.
I need to catch error 401 Code of response so that I can retry after getting a new token from token endpoint. I am using fetch method get data from API.
const request: Request = new Request(url.toString(), {
headers: this.defaultRequestHeaders,
method: "get",
mode: "cors"
});
const headers: Headers = new Headers({
"Accept": "application/json",
"Content-Type": "application/json"
});
fetch(request)
.then(function(response)
{
///Logic code
})
.catch(function(error)
{
///if status code 401. Need help here
});
You can check the status and if it's not 200 (ok) throw an error
fetch("some-url")
.then(function(response)
{
if(response.status!==200)
{
throw new Error(response.status)
}
})
.catch(function(error)
{
///if status code 401...
});
Because 401 is actually a valid response to a request to a server, it will execute your valid response regardless. Only if security issues occur, or if the server is unresponsive or simply not available will the catch clause be used. Just think of it like trying to talk to somebody. Even if they say "I am currently not available" or "I don't have that information", your conversation was still successful. Only if a security guy comes in between you and stops you from talking to the recipient, or if the recipient is dead, will there be an actual failure in conversation and will you need to respond to that using a catch.
Just separate out your error handling code so you can handle it in instances that the request was successful, but does not have the desired outcome, as well as when an actual error is being thrown:
function catchError( error ){
console.log( error );
}
request.then(response => {
if( !response.ok ){
catchError( response );
} else {
... Act on a successful response here ...
}
}).catch( catchError );
I am using the response.ok suggested by #Noface in the comments, as it makes sense, but you could check for only the response.status === 401 if you want to.
You can try this
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {
console.log('DO WHAT YOU WANT')
});
You can check the status of the response in then:
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {});
fetch(url,{
method: 'GET',
headers,
body: JSON.stringify(aData)
}).then(response => {
if(response.ok){
return response.json();
}
return Promise.reject(response);
}).catch(e => {
if(e.status === 401){
// here you are able to do what you need
// refresh token ..., logout the user ...
console.log(e);
}
return Promise.reject(e.json());
});
(function () {
var originalFetch = fetch;
fetch = function() {
return originalFetch.apply(this, arguments).then(function(data) {
someFunctionToDoSomething();
return data;
});
};})();
source
Can one use the Fetch API as a Request Interceptor?
When you want to...
catch (error) {
console.dir(error) // error.response contains your response
}
I tried to find out the solution for one of the existing question
Error Handling in HTTP Ajax Call using $fetch Javascript - I tried to reproduce the said scenario. I found the issue, that is the HTTP call triggers the HTTP OPTIONS method. Finally its not returning any HTTP Status code. I checked the Netwok, its shows an empty status code and the status signal is grayed
Refer Preflight Table Request: https://learn.microsoft.com/en-us/rest/api/storageservices/preflight-table-request
Kindly assist me how to handle the failure of HTTP OPTIONS
I tried the following code
$fetch("http://localhost:1000/SearchUsers?....")
.then(response => {
if((response != undefined) && (response != null)) {
if (response.status && (response.status === 200)) {
alert('Super');
return response.json();
} else {
alert('Hai');
return '';
}
} else {
alert('Oooops');
return '';
}
})
.catch(err => {
const errStatus = err.response ? err.response.status : 500;
if (errStatus === 404){
alert("HTTP 404");
} else {
alert("Network or Internal Server Error");
}
});
The above said code always executes the Catch block and it alerts the message "Network or Internal Server Error".
I tried with following Header information too, but it fails.
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': 'POST, GET, PUT'
}
Kindly assist me how to get the appropriate Error Status Code, in current scenario the requesting server is offline.