I would like to do some error handling on the response received from a call I am making and then move to the catch if the specific null check is hit. Something like this:
fetch('example.json')
.then(response => {
if (response.data === null) {
//go to catch
}
})
.catch(error => {
console.error("error happened", error);
})
What would be the best way to go about doing something like this? Any red flags with throwing an error inside a then block?
If you throw in a promise handler, that rejects the promise the handler returns. So:
fetch('example.json')
.then(response => {
if (response.data === null) {
throw new Error();
}
})
.catch(error => {
console.error("error happened", error);
})
What you throw will be the rejection reason the catch handler sees. It doesn't have to be an Error, but as with synchronous code, it's generally best if it is.
But, note that A) A fetch response doesn't have a data property, and B) You need to check for HTTP success and parse the JSON that was returned.
You probably want something like this:
fetch('example.json')
.then(response => {
if (!response.ok) {
// (I tend to use an specialized `Error` type here
// More on my anemic blog:
// http://blog.niftysnippets.org/2018/06/common-fetch-errors.html)
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(data => {
if (data === null) {
throw new Error("The data is null");
})
// ...do something with `data`...
})
.catch(error => {
console.error("error happened", error);
});
In a comment on the question you've said:
i was hoping there was a way to check this response object without having to trigger the 'extreme' measure of throwing an exception
You do have an alternative which is basically identical in outcome: Return a rejected promise. Here's my second code block above adapted to do that:
fetch('example.json')
.then(response => {
if (!response.ok) {
// (I tend to use an specialized `Error` type here
// More on my anemic blog:
// http://blog.niftysnippets.org/2018/06/common-fetch-errors.html)
return Promise.reject(new Error("HTTP error " + response.status));
}
return response.json();
})
.then(data => {
if (data === null) {
return Promise.reject(new Error("The data is null"));
})
// ...do something with `data`...
})
.catch(error => {
console.error("error happened", error);
});
And as with the throw version, you don't have to use an Error, it's just best practice. It can be anything you want.
If you want, you can throw an Error object from within your promise handler.
fetch('example.json')
.then(response => {
if (response.data === null) {
throw new Error('oopsie');
}
})
.catch(error => {
console.error("error happened", error); // will show "Error: oopsie"
})
Related
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
}
I'm calling an API that defines the statusCode from data instead of the response code:
{
data: {
statusCode: 422,
message: "User's not found"
},
status: 200
}
In my axios get request it's getting the status code from the status instead in data.
return axios.get(`${process.env.BASE_URL}/users`)
.then(response => {
console.log(response);
}).catch(err => {
console.log(err.message);
});
I'm getting the response but it should go to catch since it's 422.
How can I refer to the statusCode of the data response so that if it's not 200 it should go to catch statement
You can intercept the response, inspect the data and throw a custom error in this case:
// Add a response interceptor
axios.interceptors.response.use(function(response) {
if (response.data && response.data.statusCode && !(response.data.statusCode >= 200 && response.data.statusCode < 300)) throw new Error()
return response;
}, function(error) {
return Promise.reject(error);
});
// Make a GET request
axios.get(url)
.then((data) => {
console.log('data', data)
})
.catch((e) => {
console.log('error', e)
})
This way you configure your axios instance so you dont have to repeat yourself for every single request in your app
Also, you can override the status using following code. But since status validation has already executed, it will not throw errors on bad status codes
// Add a response interceptor
axios.interceptors.response.use(function(response) {
if (response.data && response.data.statusCode) response.status = response.data.statusCode
return response;
}, function(error) {
return Promise.reject(error);
});
You can handle with standard if statement inside the .then()
return axios.get(`${process.env.BASE_URL}/users`)
.then(response => {
if(response.data.statusCode===442){
...//custom error handling goes here
}else{
...//if statusCode is a success one
}
}).catch(err => {
console.log(err.message);
});
Check the response.data.statusCode value, if it is 442 then you should ideally throw an Error and let it be handled in the .catch callback.
return axios.get(`${process.env.BASE_URL}/users`)
.then(response => {
if(response.data.statusCode===442){
throw new Error(response.data.message); //using throw instead of Promise.reject() to break the control flow.
}else{
//return the data wrapped in promise
}
})
.catch((err) => {
console.log(err.message);
return Promise.reject(err.message);
});
I'm writing a REST API and trying to correctly handle any errors.
When the API call succeeds, the the success object is returned to the calling function and the response is send to the client. But if an error occurs, I want to return the error to the calling function so I can send an error message to the client.
router.delete('/project', (req, res) => {
return DeleteProject(userId, projectId)
.then((response) => {
//handle response
});
});
DeleteProject: (userId, projectId) => {
return deleteProject(userId, projectId)
.then((response) => {
return response
})
.catch((error) => {
console.log('Error in DeleteProject:', error) // This happens.
return error; // this doesn't happen.
})
},
function deleteProject(userId, projectId) {
return Project.deleteOne( ... delete the project... )
.then((response) => {
return response
})
.catch((error) => {
return error
})
}
The .catch(error) in the middle function above, DeleteProject(), gets triggered when an error occurs (ie, the console log happens), but the return doesn't make it's way back to the router.
How can I return the error to be handled by the router?
You can simply remove catch methods from the other two functions, and put the catch function in the router itself. Then the error will itself propagate to your router function
router.delete('/project', (req, res) => {
return DeleteProject(userId, projectId)
.then((response) => {
//handle response
}).catch(() => {
// Add catch function here. Any error in "DeleteProject" and "deleteProject" will propagate to here
})
});
DeleteProject: (userId, projectId) => {
return deleteProject(userId, projectId)
.then((response) => {
return response
});
// Remove catch function
},
function deleteProject(userId, projectId) {
return Project.deleteOne( ... delete the project... )
.then((response) => {
return response
});
// Remove catch function
}
To propagate errors through promise chains you need to throw them. In your catch handler, when you return the error rather than throwing it, you'e setting the (successfully) resolved value of the promise to be the error.
I am trying to handle 500 internal server errors inside fetch. If an internal error occurs, the server responds with a message. I want to extract that message.
const req = new Request(url, {
method: node.method,
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
fetch(req)
.then((response) => {
if (response.status === 500) {
// res.json extracts the body from the response as a promise, chain
// .then on it and throw an error to be caught in the lower catch.
response.json()
.then((json) => {
const { message, stackTrace } = json;
throw new ServerException(message, stackTrace); // note 1
})
.catch((error) => {
return Promise.reject(RawException(error)); // note 2
});
} else {
return response.json();
}
})
.then((json) => { // note 3
dispatch(stopLoading());
dispatch(recieveResponse(typeOfRequest, json));
})
.catch((e) => {
dispatch(stopLoading());
dispatch(responseError());
dispatch(showError(e.message));
});
};
My issue is that extracting the body of the response creates a new promise, and I am unable to reject the outer promise from the inner one.
Note 1 triggers the catch method of the inner promise. Inside catch, I have tried throwing another error but it doesn't seem to work. If I throw new RawException(error) on the second noted line, nothing happens and the then method on the third noted line triggers. If I return a rejected promise as I have in the code provided, then still triggers but json is undefined.
How do I do this?
The solution is not to nest promises, but to resolve/return the .then of the outer promise with the conclusion of the inner promise.
if (response.status === 500) {
response.json() // response.json returns a promise, we chose to do nothing with its
.then((json) => { // conclusion
const { message, stackTrace } = json;
throw new ServerException(message, stackTrace); // note 1
})
.catch((error) => {
return Promise.reject(RawException(error)); // note 2
});
} else {
return response.json();
}
Should become
if (response.status === 500) {
return response.json() // return the result of the inner promise, which is an error
.then((json) => {
const { message, stackTrace } = json;
throw new ServerException(message, stackTrace);
});
} else {
return response.json();
}
The else clause can be removed as well if that syntax is preferred. ESLint complains about the else being wasteful, but I perfer the way it makes the code's branching explicit.
I have an API that includes a useful description of what went wrong when an error is raised by the server (status = 500). The description comes as part of the response text. My client code, using Aurelia, calls the api via aurelia-fetch-client using a generic method to make the call:
function callRemoteService(apiName, timeout) {
return Promise.race([
this.http.fetch(apiName),
this.waitForServer(timeout || 5000) // throws after x ms
])
.then(response => response.json() )
.catch(err => {
if (err instanceof Response) {
// HERE'S THE PROBLEM.....
err.text().then(text => {
console.log('Error text from callRemoteService() error handler: ' + text);
throw new Error(text)
});
} else if (err instanceof Error) {
throw new Error(err.message);
} else {
throw new Error('Unknown error encountered from callRemoteService()');
}
});
}
Note that I want to catch the server (fetch or timeout) errors in a consistent way, and then throw back just a simple error message to the calling view. I can invoke callRemoteService successfully, catching errors when a 500 is returned with:
callRemoteService(this.apiName, this.apiTimeout)
.then(data => {
console.log('Successfully called \'' + this.apiName +
'\'! Result is:\n' + JSON.stringify(data, null, 2));
})
.catch(err => {
console.log('Error from \'' + this.apiName + '\':',err)
});
However, I'm having trouble accessing the response text, because the fetch provides the text() method that returns a promise, and that's interfering with my otherwise happy promise chaining. The code above doesn't work, leaving me with an Uncaught (in promise) error.
Hopefully there's a good way to access that response text?
This should do the trick:
function callRemoteService(apiName, timeout = 5000) {
return Promise.race([
this.http.fetch(apiName)
.then(
r => r.json(),
r => r.text().then(text => throw new Error(text))
),
this.waitForServer(timeout)
]);
}
by the way, I like what you're doing with Promise.race- nice technique!