whatwg-fetch response.ok === true even if server is offline - javascript

I make requests using whatwg-fetch and I'm trying to check response.status === 0 in case response.ok is false to see if the server is currently offline and notify users accordingly.
Problem is, response.status is always 200 and response.ok is always true when the server's offline.
const interpretJsonResponse = (response, callback):object => {
const responseClone = response.clone();
if (response.ok) {
response.json().then((json) => callback(null, respondJson(json))).catch(callback);
} else {
// Response is not ok. Callback with a generic error to show to the user
// and log the error for detailed info
if (response.status === 0) {
// Backend server is not up
callback('Backend server is not responding. Please retry later!');
} else {
callback(`Error in request: ${response.statusText}`);
}
}
// Return a clone to allow usage of .json() again if necessary
return responseClone;
};
I'm also getting a JSON parse error since the response is empty, given the lack of servers to reply. What am I / could I be missing?
Example Response object:
"url":"http://localhost:8000/client_v1/auth/",
"status":200,
"statusText":"OK",
"headers": {}
"ok":true,
"body":{
"_readableState":{
"highWaterMark":16384,
"buffer":[],
"length":0,
"pipes":null,
"pipesCount":0,
"flowing":null,
"ended":false,
"endEmitted":false,
"reading":false,
"sync":false,
"needReadable":true,
"emittedReadable":false,
"readableListening":false,
"objectMode":false,
"defaultEncoding":"utf8",
"ranOut":false,
"awaitDrain":0,
"readingMore":false,
"decoder":null,
"encoding":null
},
"readable":true,
"_events":{},
"_writableState":{
"highWaterMark":16384,
"objectMode":false,
"needDrain":false,
"ending":false,
"ended":false,
"finished":false,
"decodeStrings":true,
"defaultEncoding":"utf8",
"length":0,
"writing":false,
"corked":0,
"sync":true,
"bufferProcessing":false,
"writecb":null,
"writelen":0,
"buffer":[
],
"pendingcb":0,
"prefinished":false,
"errorEmitted":false
},
"writable":true,
"allowHalfOpen":true,
"_transformState":{
"needTransform":false,
"transforming":false,
"writecb":null,
"writechunk":null
}
},
"bodyUsed":false,
"size":0,
"timeout":0,
"_raw":[],
"_abort":false
EDIT: Added full function
EDIT #2: Added example whatwg-fetch Response object

I just found this caveat, in fetch, which could be useful to you:
Caveats
The fetch specification differs from jQuery.ajax() in mainly two ways that bear keeping in mind:
The Promise returned from fetch() won't reject on HTTP error status even if the response is a HTTP 404 or 500. Instead, it will resolve normally, and it will only reject on network failure, or if anything prevented the request from completing.
So, I should simply check the response for emptyness (if response is empty, as you say):
if (response === null) {
return callback('Empty reponse from backend server. Please retry later!');
}
if (response.ok) {
...

Related

nodejs app crash on openai dall-e 2 api rejected request

I'm surely dumb, but I'm not able to figure out how to handle openai api rejected requests
( for the context, dall-e 2 is an image generator )
when user tries to generate forbidden images, my nodejs app just exits
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}
it gives me something like that on the exit :
data: {
error: {
code: null,
message: 'Your request was rejected as a result of our safety system. Your prompt may contain text that is not allowed by our safety system.',
param: null,
type: 'invalid_request_error'
}
}
tried using response.catch and try catch without success, the app just exits everytime
I at least want to ignore this error in the first place
in a second hand, I would like to console.log the given message (data.error.message)
I don't know what to do to by honest, don't even understand why try catch isn't working
With the details given, my guess would be that the Promise returned by getImages is being rejected. You could debug this a bit by adding some additional logs into your .catch callback and catch statement.
How to do this really depends on what you're trying to do with this api, the code as it's currently written would log something and exit no matter what happens.
There's a couple ways to handle this
Use your .catch to handle the error. Utilizing promise chainability you can get something like this
openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
user: msg.author.id,
})
.catch((e) => {
if (e.data.error.message.includes('safety system')) {
return 'something'
}
console.error(e)
})
If you need the response object, the asnwer might be different. Looks like the openai package is built on axios and you can pass axios options into it. See https://axios-http.com/docs/handling_errors and the Request Options section of https://npmjs.com/package/openai
EDIT
I found my solution thanks to #JacksonChristoffersen
Basically I was getting http status 400
I just added request options from axios to validate http status smaller than 500
Here's the solution:
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
},{
validateStatus: function (status) {
return status < 500; // Resolve only if the status code is less than 500
}
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}

Why Request API when error always goes to catch

I have some questions about requesting API from the server. I make a function for request API, the example I have a request API login when the user fills the email wrong, response API is "email or password is wrong!", when I try in postman is success the response but when I try in my code the response always from the catch, not from response API. My code for request API like below
const handleSubmitLogin = async (input) => {
try {
const result = await axios.post(`${BASE_URL}/users/client/login`, input);
if (result.status == 200 || result.status === "success" || result.status == 201) {
await setAuthKey(result.data.data.token);
await setLoggedUser(JSON.stringify(result.data.data));
dispatch(setUserLogin());
dispatch(setDataLogin(result.data.data));
} else {
setModalActive({ status: true, data: result.message });
}
} catch (error) {
console.log(error);
setModalActive({ status: true, data: translations["please.try.again"] });
}
};
when a user fills an email or password wrong, the response is always from the catch response not from the API response. Can anyone give suggestions for this case?
Edit:
This is result from when user wrong password
If your API responds with a non-successful status code (>= 400), Axios will reject the promise and your code will go into the catch block.
You can still access the response data via error.response.data. See Axios - Handling Errors
try {
const result = await axios.post(`${BASE_URL}/users/client/login`, input);
// etc...
} catch (err) {
console.warn("login", error.toJSON());
setModalActive({
status: true,
data: error.response?.data?.message ?? translations["please.try.again"],
});
}
It's important to use optional chaining since the error may not have a response or data depending on what exactly failed.

Trying to access error response 500 axios

I am not able to access the response of error - 500 in axios
export const dowloadFilePDF = (data) => {
return axios
.request({
method: 'GET',
url: `${basePath + data[0]}`,
responseType: 'blob',
headers: { Authorization: Authorization },
})
.then(response => {
console.log(response)
let fileName = response.headers['content-disposition']?.split(';')[1]?.split('=')[1]?.split('"').join('')
fileName = fileName ? fileName : 'data.pdf'
fileDownload(response.data, fileName)
})
.catch((error) => {
console.log(error.response.data)
})
}
I am not getting the response instead its returning as
data : Blob {size: 215, type: 'application/json'}
According to the documentation, you can't assume error.response will be filled in. Here's the code the documentation shows with the inline comments explaining it:
Handling Errors
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
There's another aspect to this as well: You're calling catch on the promise returned by then, not on the promise returned by axios. If the axios promise is rejected, you'll reach that rejection handler, but you'll also reach it if the axios promise is fulfilled but then your fulfillment handler throws an error (or returns a promise it ultimately rejects). In that latter case, the error probably won't have a response property at all.
the best way to catch errors instead of trying a lot of lines of code in the catch method by promise is using the tools in the Axios names interceptor.
interceptor has two property request and response. In response we can simulate the errors status and based on the status code we can do whatever we want. for example :
axios.interceptors.response.use(null, error => {
console.log("error : " , error);
const expectedError = error.response && error.response.status >= 400 &&
error.response.status < 500;
if (expectedError) {
return Promise.reject(error);
}
alert("unexpected error is happen");
});
if you need more help here is the original link

How to avoid multiple token refresh requests when making simultaneous API requests with an expired token

API request using JWT is implemented in flask and Vue.js.
The JWT is stored in a cookie, and the server validates the JWT for each request.
If the token has expired, a 401 error will be returned.
f you receive a 401 error, refresh the token as in the code below,
The original API request is made again.
The following code is common to all requests.
http.interceptors.response.use((response) => {
return response;
}, error => {
if (error.config && error.response && error.response.status === 401 && !error.config._retry) {
error.config._retry = true;
http
.post(
"/token/refresh",
{},
{
withCredentials: true,
headers: {
"X-CSRF-TOKEN": Vue.$cookies.get("csrf_refresh_token")
}
}
)
.then(res => {
if (res.status == 200) {
const config = error.config;
config.headers["X-CSRF-TOKEN"] = Vue.$cookies.get("csrf_access_token");
return Axios.request(error.config);
}
})
.catch(error => {
});
}
return Promise.reject(error);
});
When making multiple API requests at the same time with the token expired
Uselessly refreshing the token.
For example, requests A, B, and C are executed almost simultaneously.
Since 401 is returned with each request,
Each interceptor will refresh the token.
There is no real harm, but I don't think it's a good way.
There is a good way to solve this.
My idea is to first make an API request to validate the token expiration,
This method is to make requests A, B, and C after verification and refresh are completed.
Because cookies are HttpOnly, the expiration date cannot be verified on the client side (JavaScript).
Sorry in poor english...
What you'll need to do is maintain some state outside the interceptor. Something that says
Hold up, I'm in the middle of getting a new token.
This is best done by keeping a reference to a Promise. That way, the first 401 interceptor can create the promise, then all other requests can wait for it.
let refreshTokenPromise // this holds any in-progress token refresh requests
// I just moved this logic into its own function
const getRefreshToken = () => http.post('/token/refresh', {}, {
withCredentials: true,
headers: { 'X-CSRF-TOKEN': Vue.$cookies.get('csrf_refresh_token') }
}).then(() => Vue.$cookies.get('csrf_access_token'))
http.interceptors.response.use(r => r, error => {
if (error.config && error.response && error.response.status === 401) {
if (!refreshTokenPromise) { // check for an existing in-progress request
// if nothing is in-progress, start a new refresh token request
refreshTokenPromise = getRefreshToken().then(token => {
refreshTokenPromise = null // clear state
return token // resolve with the new token
})
}
return refreshTokenPromise.then(token => {
error.config.headers['X-CSRF-TOKEN'] = token
return http.request(error.config)
})
}
return Promise.reject(error)
})

Handling 401 in axios when using both interceptors and transformResponse

I have a method for fetching data from server, basically just another GET request. However I use transformResponse in order to modify data to suit my needs.
Also I have interceptor for dealing with 401 error responses.
The thing is, when server sends 401 response on my GET request, transformResponse code runs and tries to modify server response, which is obviously is not what transformResponse expected. This leads to the situation when interceptor receives javascript error and doesn't get full http response (error.response returns undefined).
Here is two code samples.
Getting data from server:
findOak() {
console.log('find oak')
return axios.get(`${baseURL}/`, {
params: {
is_oak: true
},
transformResponse: (response) => {
console.log('transformResponse')
return companyTransformer(response.data[0])
}
}).then(response => response.data)
}
Setting up interceptors:
axios.interceptors.response.use(
response => response,
error => {
console.log('interceptor')
console.log(error.response)
if (error.response && error.response.status === 401 && authenticated()) {
logout()
window.location.href = '/'
} else {
return Promise.reject(error)
}
}
)
So the error.response is undefined, instead of server response in holds the error: Uncaught (in promise) TypeError: Cannot read property '0' of undefined which happens in transformResponse.
How to deal with this case?

Categories

Resources