Handling network errors with fetch - javascript

I'm using the fetch API with Redux with the thunk middleware and I think the way to handle network errors is kinda strange. This is a simple async action:
const fetchData = (id) => (dispatch) => {
dispatch(requestData())
return fetch(`/my/api/${ id }`)
.then(response => response.json())
.then(json => dispatch(receiveDataSuccess(json, id)))
.catch(err => dispatch(receiveDataFail(err, id)))
}
But if there's a network error like 404 or 500 status it gets converted to a JSON parsing error like SyntaxError: Unexpected end of JSON input. To get around it I changed my action to something like this:
const fetchData = (id) => (dispatch) => {
dispatch(requestData())
return fetch(`/my/api/${ id }`)
.then(response => {
switch (response.status) {
case 200:
return response.json()
default:
// Here I just throw an error for it
// to be catched by the promise chain
throw {
status: response.status
}
}
})
.then(json => dispatch(receiveDataSuccess(json, id)))
.catch(err => dispatch(receiveDataFail(err, id)))
}
This way I can show a more meaningful error message to the user than just a "There was an error".
Is it the proper way to handle this or am I missing something?

Related

Show an errorModal if there is a server error

I'm wanting to display an Error modal that will render if there is a server error on submit of a panel.
I'm wondering whether the correct way to go about it is in the catch statement?
basic code I have is:
useEffect(() => {
if (!isOpen)
setShowSuccessConfirmation(false);
}, [isOpen]);
const enableSubmission = currentDocuments.length > 0;
const submitPanel = () => {
const documentIds = currentDocuments.map(doc => doc.documentId);
setIsSubmitting(true);
Trading(panelState!.code, documentIds)
.then(() => {
setShowSuccessConfirmation(true);
})
.finally(() => {
setIsSubmitting(false);
});
};
in my panel I have my ErrorModal:
<ErrorModal show={showModal} onClose={() => { setShowModal(false) }} />
what I think I want to do is add a catch like:
.catch((error) => {
if (error.status === 500) {
setShowModal(true);
setShowSuccessConfirmation(false)
}
})
I don't get any errors but this doesn't seem to work.
A 500 status code is an internal server error, it is just a way for the server to tell you what happen while the request was being processed in the same way that 200, 204, 302, etc are. I've had projects where only 204 where valid statuses codes and other where any 3XX status code was considered an error.
If you want to show a modal under those conditions, you will need to detect this situation which means that you will need to do the check in the then:
Trading(panelState!.code, documentIds)
.then((response) => {
// Here I am assuming you have access to the response object
if (response.status === 500) {
setShowModal(true);
setShowSuccessConfirmation(false)
} else {
setShowSuccessConfirmation(true);
}
})
.finally(() => {
setIsSubmitting(false);
});
Alternatively, you can throw an error in the then part and catch it in the catch:
Trading(panelState!.code, documentIds)
.then((response) => {
// Here I am assuming you have access to the response object
if (response.status === 500) {
throw new Error(response.status);
}
setShowSuccessConfirmation(true);
})
.catch((error) => {
// You can access the status code from here with error.message
setShowModal(true);
setShowSuccessConfirmation(false)
})
.finally(() => {
setIsSubmitting(false);
});
For a real error to be caught in catch, a communication problem should happen (f.e. a timeout). Which means that you need to consider such errors when/if you use the catch part.

Test the status code of a real request to an API with Jest

Hello I'm trying to test this API call but I don't know how to test for the status code of the response since it is a real (and it has to stay like that) API call and not a mock one
this is the function I'm testing:
export const getDataFromApi = (url) => {
return axios.get(url)
.then(({ data }) => data)
.catch(err => console.log(err.toString()));
}
and this is the test:
describe('Read data from API', () => {
test('Get result of the API call', (done) => {
const apiUrl = "https://rickandmortyapi.com/api/character";
getDataFromApi(apiUrl)
.then(data => {
expect(data).toBeDefined();
expect(data.results.length).toBeGreaterThan(0);
done();
});
});
});
how can I expect if the status code of data is 200 or if is another status code?
also is necessary for me to leave that done after the execution of the function? I know with call backs I have to put it but with this promise I'm not sure
Axios has a single response object returned in both the success and error paths which contains the HTTP status code. An error is raised if the response is not in the 2xx range.
You can plumb the status code as a return object from your getDataFromApi() wrapper function, but you'll probably want the full response object for other checks (like headers). I recommend getting rid of the wrapper altogether.
Without the wrapper, here's 2 different status checks using promises, one for success and one for failure:
describe('Read data from API', () => {
test('Get successful result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character";
await axios.get(apiUrl)
.then(r => {
expect(r.data).toBeDefined();
expect(r.data.results.length).toBeGreaterThan(0);
expect(r.status).toBeGreaterThanOrEqual(200);
expect(r.status).toBeLessThan(300);
})
.catch(e => {
fail(`Expected successful response`);
});
});
test('Get failure result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character-bad";
await axios.get(apiUrl)
.then(r => {
fail(`Expected failure response`);
})
.catch(e => {
if (e.response) {
expect(e.response.status).toBeGreaterThanOrEqual(400);
expect(e.response.status).toBeLessThan(500);
} else {
throw e;
}
});
});
});

Proper Javascript promise construction using finally()

I am building out an Express API built with the mssql package.
If I don't call sql.close() then I get the following error:
Error: Global connection already exists. Call sql.close() first.
I'd like to keep the endpoints easy to follow and maintain and like the following pattern using a finally promise pattern.
const sql = require("mssql")
const config = require("../config")
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
})
.catch(err => res.send(err))
.finally(sql.close())
})
However, this generates the following error:
{
"code": "ENOTOPEN",
"name": "ConnectionError"
}
The following code works, but it seems a bit clumsy to define sql.close multiple times in the same function.
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
sql.close()
})
.catch(err => {
res.send(err)
sql.close()
})
})
Is there a way to call sql.close as part of the promise chain after either a response or error is sent with res.send?
.finally accepts function, you passing result of function
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
})
.catch(err => res.send(err))
.finally(() => sql.close()) // FIX HERE
})

How control javascript error when json is empty

I am parsing a JSON file in javascript. Every 5 minutes the JSON is autoimatically updated with new data, during the time it is being updated the JSON is blank (for a about 2 seconds).
I get this error
Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at fetch.then.res
This is the code in javascript for parsing the JSON:
fetch("http://location/file/data.json")
.then(res => res.json())
.then(data => {
//do something
})
How do I control this so that it doesn't flag this error? I still want an a customer error to appear using console.log(Error()).
Any help is appreciated.
This should do the trick. then() takes a second callback function as argument that receives the error object.
fetch("http://location/file/data.json")
.then(res => res.json(), err => console.log(err))
.then(data => {
//do something
}, err => console.log(err))
EDIT: As per comment, this way is preferred. Can read more about using promises in this link
fetch("http://location/file/data.json")
.then(res => res.json())
.then(data => {
//do something
})
.catch(err => console.log(err)
You can add .catch into your processing:
fetch("http://location/file/data.json")
.then(res => res.json())
.then(data => {
// do something
})
.catch(err => console.log(err.message))
EDIT: err.message instead of JSON.stringify(err).

How to include both a parsed response and original response headers in fetch errors [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 6 years ago.
I have the following promise chain:
return fetch(request)
.then(checkStatus)
.then(response => response.json())
.then(json => ({ response: json }))
.catch(error => ({ error }))
Where checkstatus() checks if the request was successful, and returns an error if it wasn't. This error will be caught and returned. But, the problem is that I want to add the both response.statusText and the results of response.json() to the error. The problem is that when I parse it I lose the original response in the chain since I have to return response.json() because it's a promise.
This is what checkstatus does currently:
const checkStatus = response => {
if (response.ok) return response
const error = new Error('Response is not ok')
// this works because the response hasn't been parsed yet
if (response.statusText) error.message = response.statusText
// an error response from our api will include errors, but these are
// not available here since response.json() hasn't been called
if (response.errors) error.errors = response.errors
throw error
}
export default checkStatus
How do I return an error with error.message = response.statusText and error.errors = response.json().errors?
Here's my helper for sane fetch error handling, fetchOk:
let fetchOk = (...args) => fetch(...args)
.then(res => res.ok ? res : res.json().then(data => {
throw Object.assign(new Error(data.error_message), {name: res.statusText});
}));
Which I then substitute for fetch.
let fetchOk = (...args) => fetch(...args)
.then(res => res.ok ? res : res.json().then(data => {
throw Object.assign(new Error(data.error_message), {name: res.statusText});
}));
fetchOk("https://api.stackexchange.com/2.2/blah")
.then(response => response.json())
.catch(e => console.log(e)); // Bad Request: no method found with this name
var console = { log: msg => div.innerHTML += msg + "<br>" };
<div id="div"></div>
It doesn't load the data unless there's an error, making it a direct replacement.
I use the new async/await syntax, since that reads in a more intuitive way:
async fetchData(request) {
try {
const response = await fetch(request)
const data = await response.json()
// return the data if the response was ok
if (response.ok) return { data }
// otherwise return an error with the error data
const error = new Error(response.statusText)
if (data.errors) error.errors = data.errors
throw error
} catch (error) {
return { error }
}
}
It makes it very easy to handle both the promise that fetch returns as well as the promise that response.json() returns.

Categories