Update state inside catch in javascript - javascript

I'm currently working on a script that sends requests to a 3rd party website, but after sometime the cookies are invalid & it throws a 403
So I've got my script to send the request, if it throws the 403 then we send VALID cookies and then RESEND the request.
However it appears that the state.device_info remains undefined after updating it inside the catch function?
(async () => {
let state = {
'access_denied': false,
'cookies': {}
};
try {
await getCookies(state);
state.device_info = await getInfo(state).catch(async (error) => {
let localError = handleError(error, state);
if(localError === 'access_denied') {
state.access_denied = true;
//now lets unlock the request & send it again!
let post = await sendValidCookies(state);
if(post.data.success === true) {
//update state.device_info with WORKING request!
state.device_info = await getInfo(state).catch(async (error) => {
console.log('Damn we got another error!');
console.log(error);
})
if(state.device_info.status === 200) {
console.log('We got our info info using the unlocked request!');
state.access_denied = false;
}
}
}
});
if(state.device_info === undefined || state.access_denied === true) {
console.log('We have an undefined value!');
console.log(state.device_info); //undefined
console.log(state.access_denied); //false
return false;
}
} catch(error) {
console.log('major error!');
}
})();

Instead of defining it inside catch, just return the value. This returned value will be assigned to state.device_info
state.device_info = await getInfo(state).catch(async (error) => {
let localError = handleError(error, state);
if(localError === 'access_denied') {
state.access_denied = true;
//now lets unlock the request & send it again!
let post = await sendValidCookies(state);
if(post.data.success === true) {
//Get result from WORKING request
const result = await getInfo(state).catch(async (error) => {
console.log('Damn we got another error!');
console.log(error);
});
if(state.device_info.status === 200) {
console.log('We got our info info using the unlocked request!');
state.access_denied = false;
}
return result;
}
}
});

Since you're using the await keyword in front of your method call getInfo(state) and you're calling .catch(...) right after, I assume getInfo(state) returns a Promise.
There are two ways you can handle Promise in JavaScript:
By explicitly call .then(result => {...}) while catching errors with .catch(error => {...}).
By putting the await keyword in front of your method call. By using this syntax, and if you want to catch errors (which you should do), you need to use the try { } catch (error) { } syntax.
I've never mix those two methods like you did and I think this might be the origin of the issue you're experiencing.
For your case, it should look like this:
try {
state.device_info = await getInfo(state);
} catch (error) {
// Your logic here...
}

Related

In JS fetch API promise style, how to get the raw body when the json() function failed

I know this can be solved by writing all codes to async-await style, then can simply write let text = await res.text(); then try catch the JSON.parse(text) and then do decision.
But here I just want to know if there is any way we can achieve that in .then/.catch style.
Consider the below code:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then(res => {
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e)
})
}
catch (e) {
console.error("Catch 3", e)
}
}
if we execute this function in the browser devtools(F12) with await test(), then there will be an error catch by the "Catch 2" clause. But in the error detail we can only see some logs like JSON parse error.
We cannot see the full text of the response body.
Is there any way that can get the text when the JSON parsing failed?
Your best bet is to look at the response in your devtools' network tab. That will show you the full response.
But if you want to do it in code, you can separate reading the response from parsing it by using the text method instead of the json method, then parsing the text yourself.
The parsing error may be down to the fact you aren't checking for HTTP success. As I noted on my old anemic blog here, fetch only rejects its promise on network errors, not HTTP errors (like 404, 500, etc.). To check for HTTP success, look at the ok or status properties.
Here's the minimal-changes version separating reading the response from parsing it, and checking for HTTP success before reading it at all:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then((res) => {
if (!res.ok) { // ***
throw new Error(`HTTP error ${res.status}`); // ***
} // ***
return res.text(); // ***
})
.then((text) => {
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text); // ***
return data.results.length;
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
})
.catch((e) => {
console.error("Catch 2", e);
});
// ...do something with `n`...
} catch (e) {
console.error("Catch 3", e);
}
}
But a couple of things there:
I wouldn't mix async/await with explicit promise callbacks like that.
With that and with your original code, errors will result in n receive the value undefined, because the catch handlers (and my new try/catch block in the then handler) don't return anything.
Instead:
async function test() {
try {
const res = await fetch("https://stackoverflow.com");
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const text = await res.text();
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text);
const n = data.results.length;
// ...do something with `n`...
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
} catch (e) {
console.error("Catch 3", e);
}
}
Or if you want to respond differently to the parsing error, wrap that bit in a try/catch, etc.
You shouldn't confuse the catch which catching errors in the fetch function itself - with the response errors
fetch("/developer.mozilla.org")
.then(res => {
if (!res.ok) {
console.log("there was an error also here") <= this code also runs
console.log("response is", res);
}
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e);
})
In your case, you tried converting data -> JSON w/o success, it failed and dropped to the "catch" section.
but to inspect the response - you can dump it in the first section above where I added res.ok
I believe you could do something like this when using promise style Javascript:
const fetchDataPromise = () => {
fetch('https://stackoverflow.com').then((res) => {
res.json().then((jsonData) => {
console.log(jsonData)
}).catch((err) => {
console.error(err)
res.text().then((rawData) => {
console.log(rawData)
}).catch((err) => console.error(err))
})
})
}
Also more intuitive approach would be to use async/await (the trade-off is that you will have to do the API call again):
const fetchData = async () => {
try {
const res = await fetch('https://stackoverflow.com')
const jsonData = await res.json()
console.log(jsonData)
} catch (err) {
try {
console.error(err)
const res = await fetch('https://stackoverflow.com')
const rawData = await res.text()
console.log(rawData)
} catch (rawError) {
console.error(rawError)
}
}
}

get response.status in .then in Java Script

I tried to check if the status of my request is 200 (OK), but I do not know how to do these things together because the first and second .then, are not "like each other":
function f(path) {
await fetch(path)
.then(response => {
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
})
.then(response => response.json())
.then(...method for the response.json()...)
.catch(error => {
// print some error message
}
}
The second then failed and returned error.
I have a problem when I throw that.
It prints to the error to the console (when I check by changing the path to wrong path and I want to see if I treat errors).
what can I do?
You're checking it correctly in your first fulfillment handler (then callback), although I'd just use !response.ok. You don't usually need the status in the subsequent handlers.
But the problem with your first fulfillment handler is that it's not returning anything, so the subsequent fulfillment handler only sees undefined. Instead, return the promise from json():
function f(path) {
fetch(path)
.then(response => {
if (!response.ok) {
// Note: Strongly recommend using Error for exceptions/rejections
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(data => {
// ...use the data here...
})
.catch(error => {
// ...show/handle error here...
});
}
Note that you can't use await in a traditional function, only in an async function. But you don't need it if you're using .then and .catch. I've removed it above.
If for some reason you wanted the status in subsequent fulfillment handlers, you'd have to return it from the first fulfillment handler. For instance:
function f(path) {
fetch(path)
.then(response => {
if (!response.ok) {
// Note: Strongly recommend using Error for exceptions/rejections
throw new Error("HTTP error " + response.status);
}
return response.json().then(data => ({status: response.status, data}));
})
.then(({status, data}) => {
// ...use `status` and `data` here...
})
.catch(error => {
// ...show/handle error here...
});
}
In that, I've used a nested fulfillment handler on the promise from json(), and then returned an object with status and data on it.
You need to return in your then chain, which at a glance appears to be one too many. Check out the following example...
fetch(path)
.then(r => r.ok ? r.json() : Promise.reject('oops')) // .statusText, etc
.then(r => {
// [...]
})
.catch(e => console.error(e)); // oops
a) I don't think you need await keyword since you're using .then() chaining.
b) You have to return something from the first then so as to get that in the next .then()
function f(path) {
await fetch(path)
.then(response => {
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
// After doing what you need return the response
return response
}
})
.then(response => response.json())
.then(...method for the response.json()...)
.catch(error => {
// print some error message
}
}
Actually, it's not clear what your function has to do. But I think your sturggle comes from not fully understanding how promises chain works. For that, I'd recommend familiarizing yourself with this article, it helped me a lot :)
So back to your function. The elegant solution is adding simple "tap" function, that allows you to do some stuff with current response, but still it passes response further for other .then chains.
Here's the tap function:
const tap = (callback) => (value) => (callback(value), value);
And finally how you can use it:
function f(path) {
fetch(path)
.then(
tap((response) => {
if (response.status !== 200) throw new Error(response.status);
})
)
.then((response) => {
// do other stuff
})
.catch((error) => console.error(error));
}
The number of browsers that support fetch but don't support async/await is now very small, so you may be better off using this simpler syntax first, and then transpiling for legacy browsers along with your shims for fetch.
Your function becomes:
try {
const response = await fetch(path);
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
const parsed = await response.json();
// do something with parsed
}
catch(error) {
// print some error message
}
This new syntax makes it much easier to deal with errors in the different then actions:
const response = await fetch(path);
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
let parsed; // Will hold the parsed JSON
try {
parsed = await response.json();
}
catch(error) {
// Deal with parsing errors
}
try {
// do something with parsed
}
catch(error) {
// Deal with errors using the parsed result
}

How to let function continue if a return is empty

I'm working on a chat application and right now i'm working on the ability to create a new chat. To prevent the user to be able to create duplicate chats, i came up with this.
user1 is pre-defined. user2 is defined by entering it into a form. handleSubmit checks if this already exist and if "isChat" returns an id from my database the user gets redirected to the already existing chat.
I have trouble with the other part. If "isChat" is undefined my function wont continue and stops at the first await function.
async function handleSubmit(event) {
event.preventDefault();
const isChat = await getChatId(user1, user2);
if (isChat) {
setChatId(isChat);
setDefinedPartnerName(true);
} else {
await initiateNewChat(user1, user2, messages);
const chatId = await getChatId(user1, user2);
setChatId(chatId);
setDefinedPartnerName(true);
}
}
This is my fetch for this:
//Get chat ID by user1 and user2
export async function getChatId(user1, user2) {
return fetch(`/api/${user1}/${user2}`, {
method: 'GET'
})
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response;
})
.then(response => response.json());
}
This function cant return anything if there are no records for these two users.
Maybe the error fell through "throw"?
try:
export async function getChatId(user1, user2) {
return fetch(`/api/${user1}/${user2}`, {
method: 'GET'
})
.then(response => {
if (response.status !== 200) {
// throw new Error(response.statusText);
return false;
}else{
return response;
}
})
.then(response => response.json());
}
The syntax for error handling can also be improved:
https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
Solved it on my express route.
My express route has a try...catch function and i gave the catch part of it a response.json(false);
It is a hotfix. But I really don't want to spend a lot of time on backend stuff.
Thanks for your help everyone

Is there some way to make recursive fetch requests?

I want my fetch request to have some sort of retry system if it somehows fails based on the HTTP code of the response (for example: not 200). It looks something like this:
fetch('someURLWithAJSONfile/file.json')
.then(function (res) {
console.log(res.status);
if (res.status !== 200) {
console.log("There was an error processing your fetch request. We are trying again.");
// Recursive call to same fetch request until succeeds
} else {
return res.json();
}
}).then(function (json) {
data = json;
}).catch(function (err) {
console.log(`There was a problem with the fetch operation: ${err.message}`);
});
Is there a way to put the fetch request inside a custom Promise and make it call itself after checking its http response status?
Here the simple ES6 solution (since you are using fetch). The limit option means how many times you want to try your request.
var doRecursiveRequest = (url, limit = Number.MAX_VALUE) =>
fetch(url).then(res => {
if (res.status !== 200 && --limit) {
return doRecursiveRequest(url, limit);
}
return res.json();
});
doRecursiveRequest('someURLWithAJSONfile/file.json', 10)
.then(data => console.log(data))
.catch(error => console.log(error));
You can do this by wrapping your call to fetch in a named function that returns the promise created by fetch. Consider:
function fetchWithRetry(url, retryLimit, retryCount) {
retryLimit = retryLimit || Number.MAX_VALUE;
retryCount = Math.max(retryCount || 0, 0);
return fetch(url).then(function (res) {
console.log(res.status);
if (res.status !== 200 && retryCount < retryLimit) {
console.log("There was an error processing your fetch request. We are trying again.");
return fetchWithRetry(url, retryLimit, retryCount + 1);
} else {
return res.json();
}
});
}
fetchWithRetry('someURLWithAJSONfile/file.json', 10).then(function (json) {
data = json;
}).catch(function (err) {
console.log(`There was a problem with the fetch operation: ${err.message}`);
});
This code wraps your existing call and takes advantage of closure scope to maintain a retry limit and count which are both optional. You then call the fetchWithRetry function with a URL just like you did your previous call to fetch. If you do not pass a retry limit it will continue endlessly. The final retryCount variable is really only used for recursion purposes and is meant to be called internally.

Error Handling in HTTP Ajax Call using $fetch Javascript

I tried to handle the Network related issues in HTTP ajax call. So, I temporarily stopped the respective API's service in IIS and I tried to call the shut downed API - http://localhost:1000/GetData.
fetch("http://localhost:1000/GetData")
.then(handleErrors)
.then(function(response) {
return response.Json();
}).catch(function(error) {
console.log(error);
});
I tried the following code too
fetch("http://localhost:1000/GetData")
.then(response => {
if(response) {
if (response.status === 200) {
alert('Super');
return response.json();
} else {
alert('Hai');
return '';
}
} else {
alert('Oooops');
return '';
}
})
.catch(function(error) {
console.log(error);
});
But its failing and directly hitting the catch block without triggering any alert and its throwing an error. Moreover the response.json(); is in Success block, I don't know how its executed.
TypeError: response.json is not a function
Stack trace:
onFetchError/<#http://192.168.4.159:3000/app.0df2d27323cbbeada2cd.js:9946:13
Kindly assist me how to check the Status code and how to handle the Network error (i.e., Network Unavailable 404, etc.,)
Referred website: https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
Based on this issue on Github, you can try to identify error types in catch block instead. So, something like this may work for your case:
fetch("http://localhost:1000/GetData")
.then(response => {
alert("Super");
return response.json();
})
.catch(err => {
const errStatus = err.response ? err.response.status : 500;
if (errStatus === 404){
// do something
} else {
// do another thing
}
});

Categories