react native fetch not calling then or catch - javascript

I am using fetch to make some API calls in react-native, sometimes randomly the fetch does not fire requests to server and my then or except blocks are not called. This happens randomly, I think there might be a race condition or something similar. After failing requests once like this, the requests to same API never get fired till I reload the app. Any ideas how to trace reason behind this. The code I used is below.
const host = liveBaseHost;
const url = `${host}${route}?observer_id=${user._id}`;
let options = Object.assign({
method: verb
}, params
? {
body: JSON.stringify(params)
}
: null);
options.headers = NimbusApi.headers(user)
return fetch(url, options).then(resp => {
let json = resp.json();
if (resp.ok) {
return json
}
return json.then(err => {
throw err
});
}).then(json => json);

Fetch might be throwing an error and you have not added the catch block. Try this:
return fetch(url, options)
.then((resp) => {
if (resp.ok) {
return resp.json()
.then((responseData) => {
return responseData;
});
}
return resp.json()
.then((error) => {
return Promise.reject(error);
});
})
.catch(err => {/* catch the error here */});
Remember that Promises usually have this format:
promise(params)
.then(resp => { /* This callback is called is promise is resolved */ },
cause => {/* This callback is called if primise is rejected */})
.catch(error => { /* This callback is called if an unmanaged error is thrown */ });
I'm using it in this way because I faced the same problem before.
Let me know if it helps to you.

Wrap your fetch in a try-catch:
let res;
try {
res = fetch();
} catch(err) {
console.error('err.message:', err.message);
}
If you are seeing "network failure error" it is either CORS or the really funny one, but it got me in the past, check that you are not in Airplane Mode.

I got stuck into this too, api call is neither going into then nor into catch. Make sure your phone and development code is connected to same Internet network, That worked out for me.

Related

JavaScript: Is there a way to detect if a response contains error messages in json format when it is not ok

I am writing a simple wrapper around fetch.
async function apiCall(
endpoint: string,
{
data,
headers: customHeaders,
...customConfig
}: { data?: Object; headers?: Object } = {}
) {
const config = {
method: data ? 'POST' : 'GET',
body: data ? JSON.stringify(data) : undefined,
headers: {
'content-type': data ? 'application/json' : undefined,
...customHeaders,
},
...customConfig,
}
return fetch(endpoint, config as any).then(async (response) => {
if (response.ok) {
const json = await response.json() // 🤔
return json
} else {
// 👇 🚨 what if `response` contains error messages in json format?
return Promise.reject(new Error('Unknown Error'))
}
})
}
it works fine. The problem is with this snippet
return fetch(endpoint, config as any).then(async (response) => {
if (response.ok) {
const json = await response.json()
return json
} else {
// 👇 🚨 what if `response` contains error messages in json format?
return Promise.reject(new Error('Unknown Error'))
}
})
if response is not ok, it rejects with a generic Error. This is because by default, window.fetch will only reject a promise if the actual network request failed. But the issue is that, even if response is not ok, it might still be able to have error messages in json format. This depends on the backend implementation details but sometimes you are able to get the error messages in the response body by response.json(). Now this use case is not covered in the wrapper I built.
So I wonder how I am going to be able to account for that? I guess you can do something like
fetch(endpoint, config as any).then(async (response) => {
if (response.ok) {
const json = await response.json()
return json
} else {
try {
const json = await response.json()
return Promise.reject(json)
} catch {
return Promise.reject(new Error('Unknown Error'))
}
}
})
but I wonder if there is some more elegant way to do that?
Lastly, I am very aware of libraries like Axios. I built this partly to satisfy my intellectual curiosity.
Btw, a lightly unrelated question but I wonder if these two are equivalent
if (response.ok) {
const json = await response.json()
return json
}
if (response.ok) {
return response.json()
}
Someone flagged my question as a duplicate of this question. In fact they are not the same. I did not make the same assumption as that question did about the API call returning JSON data both on success and on failure. My question is about exactly how we should do in cases where we cannot make such an assumption.
That the response is not ok doesn't prevent you from consuming its body as JSON so your last snippet is indeed how it should be handled.
Now you ask for something "more elegant", well it may not be more elegant but a less redundant way to write the same would be:
fetch(endpoint, config as any).then(async (response) => {
if (response.ok) {
return response.json(); // there is no need to await the return value
}
else {
const err_message = await response.json() // either we have a message
.catch( () => new Error( "Unknown Error" ) ); // or we make one
return Promise.reject( err_message );
}
})
And regarding the last question, yes both are equivalent, the await version doing one more round-trip through the microtask-queue and making your code a bit longer, but for all that matters we could say they are the same.
I'd simplify Kaiido even further, fix some bugs, and add a custom error handler.
class ApiCallError extends Error {
response: Response;
body: any;
httpStatus: number;
constructor(response, body) {
super('HTTP error: ' + response.status);
this.httpStatus = response.status;
this.response = response;
this.body = body;
}
}
async function apiCall(endpoint: string, options?: any) {
const config = {
// do your magic here
}
const response = await fetch(endpoint, config);
if (!response.ok) {
throw new ApiCallError(response, await response.json());
}
return response.json();
}
Changes:
You don't need to catch errors if you're just going to throw again.
You don't need .then() if you support await, and this will make your code a lot simpler.
There's really no point in Promise.resolve and Promise.reject, you can just return or throw.
You should not return plain errors, you should throw them (or make sure they are wrapped in a rejecting promise)
Even though in javascript you can 'throw anything' including strings and arbitrary objects, it's better to throw something that is or extends Error, because this will give you a stack trace.
Making all the error information available on the custom error class provides everything a user of apiCall could possibly need.

node js request-promise, retry request on error

so I have a chain of request that are sent, and once catch at the end, the problem is if I have an error i wanna retry that specific request that caught the error, I know one solution to this would be to add a catch at the end off all the request i send, and when it catches an error it retries that request, but that would lead to too many catch statements, I just want one catch statement at the end that when it catches an error it retrys the specific request
rp.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rp.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
})
Your example indicates that some requests depend on the response of a previous request. Adding a catch handler at the end of the promise chain would make it extremely difficult to retry the request and continue with subsequent requests. You need to handle the error at the request, not at the end of the promise chain. This is pretty simple to do if you wrap up the request in a helper method.
function request(opts) {
return rp(opts).catch(() => request(opts));
}
request({url: 'https://www.off---white.com/en-us/api/products/' + variant, ...options2})
.then((data) => {
// doo stuff with request
return request({method: 'POST', url: 'https://www.off---white.com/en-us/api/bags/' + bagId + '/items', ...options2});
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
});
It's not really clear what options2 is and why you use it as the request body in the second request, so this may not work exactly as you would expect, but the parameters passed into request can be tweaked to fit your use case. This will also result in an infinite request loop if the request always fails, you should implement some basic error handling to avoid this infinite loop (e.g., only retry X number of times, or retry only when you get a specific error, etc.)
First of all, you will need to design your rp.get() function in a way such that somehow whenever there is an error, the error object must have an identifier (stored as type key) as of from which request the error is propagated, then on a conditional basis, you can handle the error in a single catch() block accordingly.
const rpWrapper = (type) => { //add other required params
rp.get() //pass required params
.then( (res) => result )
.catch( (err) => { throw { type, err }) }
}
Then, modify your original code to something like,
rpWrapper.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rpWrapper.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
if(error.type === "request1") { //handle error on first req }
if(error.type === "request2") { //handle error second req }
})

How to cleanly handle an expected error in fetch?

I need to query some data via a fetch() call but I am never sure whether the request will be successful, HTTP-wise: while the server is up, the URL may (legitimely) hit a non-existing page.
I would like to cleanly handle the case and my current approach is by raising an exception:
// the URL is just an example, I did not have anything CORS-enabled (and unavailable) handy, thus the no-cors mode
fetch(`https://cdnjs.com/libraries/sdfsdfsfsdfsdfsdfdf`, {
mode: 'no-cors'
})
.then(r => {
if (!r.ok) {
console.log("page does not exist")
throw Error();
}
// if the page exists, it will return JSON data
return r.json();
})
.then(r => {
console.log(r)
// things with the JSON happen here
})
.catch(err => null)
I was hoping to just return after Page does not exist, but the (empty) return would then be caught by the next then().
Is this the correct way to exit from a fetch() when the requested URL is not available?
Yes, that looks about right. I would recommend to use functions for your thens.
It makes the fetch more compact and easier to read.
const url = 'some url';
fetch(url)
.then(handleErrors)
.then(parseJSON)
.then(update)
.catch(displayErrors);
function handleErrors(res){
if(!res.ok){
throw Error(`${res.status}: Couldn't load URL.`);
}
return res;
}
function parseJSON (res){
return res.json().then(function(parsedData){
return parsedData.results[0];
})
}
function update (){
//do something with the data
}
function displayErrors(err){
console.log(err);
}

axios.all spread and catch all

I'm using .all method of popular library 'axios' for handling my ajax requests.
But how can I handle errors in case all requests got 404?
for example:
axios.all([
axios.get('http://some_url'),
axios.get('http://another_url'),
])
.then(axios.spread((someUrl, anotherUrl) => {
// ... boring stuff goes there
}))
.catch(() => {
//... error goes there
});
So, seems only one error has ben "catched".
How can I catch them all? Or maybe there any kinda .finally?
The problem (as you already know) is that you will get into catch block as soon as the first promise rejects, making it impossible to collect all failed responses in the same catch. However, you still can handle failed promises manually to aggregate errors and throw afterwards.
Check it this will work for you:
const promises = [
axios.get('http://some_url'),
axios.get('http://another_url'),
]
const promisesResolved = promises.map(promise => promise.catch(error => ({ error })))
function checkFailed (then) {
return function (responses) {
const someFailed = responses.some(response => response.error)
if (someFailed) {
throw responses
}
return then(responses)
}
}
axios.all(promisesResolved)
.then(checkFailed(([someUrl, anotherUrl]) => {
console.log('SUCCESS', someUrl, anotherUrl)
}))
.catch((err) => {
console.log('FAIL', err)
});
You will get into catch block if at least one of the promises fails. You can find one which one by checking err array of responses.
I don't think this is possible due to the fail fast behaviour of Promise.all. If any of your requests fail, they will automatically be the culprit and the result in the catch.
Promise.all([
Promise.reject(Error('1')),
Promise.reject(Error('2')),
Promise.reject(Error('3'))
]).then((results) => {
console.log(results)
}, (error) => {
console.log(error.message)
})
This resulting code will always print 1 as it is the first to fail.I think a similar feature was requested on the repo and they said it wasn't possible.
I was going to leave this as a comment but don't have a high enough reputation yet.
The solution from #dfsq did not work for me because it throws all requests when one has an error. I changed his code so every request either gets resolved or throws an error. #dfsq please review this answer if the code is correct, since I built it on your solution.
const promises = [
axios.get('http://some_url'),
axios.get('http://another_url'),
]
const promisesResolved = promises.map(promise => promise.catch(error => ({ error })))
function checkFailed (then) {
return function (responses) {
responses.forEach(response => {
if (response.error)
throw response;
return then(response);
})
}
}
axios.all(promisesResolved)
.then(checkFailed(response => {
console.log('SUCCESS', response)
}))
.catch((err) => {
console.log('FAIL', err)
});

How to get in catch method in Fetch API when there is no internet?

I am using Fetch API for requests and I want to handle an error when internet is disabled. But I didn't get any response in THEN function and no error message in CATCH function. How can I handle this issue. I am trying do thid in React Native. Thank you before hand
Checkout the NetInfo API available with React Native. You should be able to use the isConnected method to check if it's connected to the internet before making a request.
import {NetInfo} from 'react-native'
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
if (isConnected) {
fetch(...)
}
});
I was able to replicate the catch never getting called when the WiFi is off. Implementing a solution like this to manually throw an error if the request never returns could be a way around this. See this discussion on handling Timeouts with Fetch.
let test = () => {
return new Promise((resolve, reject) => {
let id = setTimeout(() => reject('Timeout'),3000)
fetch('https://swapi.co/api/people/1/')
.then(res => {
resolve(res)
clearTimeout(id)
})
.catch(err => {
reject(err)
clearTimeout(id)
})
})
}
test()
.then(() => console.log('************Success'))
.catch(() => console.warn('*************Error'))

Categories