Handling errors with async await and redux - javascript

I want to create a separate class that handles all of my api requests, posts, etc for my redux actions as sometimes done, that way my redux action code is more minimal and doing less work. However, when errors occur, I need to make sure that my actions catch these errors and dispatch them properly to my appropriate reducer. My confusion is that my api function itself is also catching errors, and I am wondering that if an error occurs first, will it first be caught in my api function, and thus I won't catch the error in my action function, thus never dispatching said error? How do I avoid this issue? Can I not catch errors in my api function and instead just divert them somehow to my action? Or do I need to dispatch requestDataFailure to my api function? Thanks.
Action:
export const fetchData = () => async (dispatch) => {
dispatch(requestData());
try {
const json = await ApiClass.getData();
dispatch(receiveData(json.data));
} catch (error) {
dispatch(requestDataFailure(error));
}
};
Api Class:
import HttpService from './httpServce';
export default class ApiClass {
static async getData() {
try {
const response = await HttpService.get('api/get-data');
return response.json;
} catch (error) {
throw Error(error.message);
}
}
}

They will (ultimately) all bubble up to your main method.
In your ApiClass you are essentially rethrowing any error, so that will bubble up into your fetchData function, and will be caught by that block.
You have a few ways of doing this:
Keep your try/catch in your ApiClass, and use that for more "friendly" error messages. Take the error from your HttpService.get and maybe read the output of the json from the server (If it's not something like a 500 error), and throw different errors based on the result. For example:
class ProductNotFound extends ApiError {
constructor(name) {
super(`This product: '${name}' was not found.`);
}
}
export default class ApiClass {
static async getProducts() {
try {
const response = await HttpService.get('api/get-products');
return response.json;
} catch (error) {
if (error.code === 404) {
throw new ProductNotFound();
}
// Check if error.response contains valid json, and if so, output a server side message?
// Unknown error, just throw
throw ApiUnknownError(error.message);
}
}
}
Now you can simply use requestDataFailure and pass the error.message, and display that to the end user.
Or, alternatively, you can simply just return response.json; not wrapped in a try/catch block. When HttpService.get rejects, it will throw an error, and you can simply use error.message to get the raw HttpService.get error message from that. Perform any redux actions based on that.

Related

JavaScript: differences between async error handling with async/await and then/catch

Just wanted to preemptively say that I am familiar with async/await and promises in JavaScript so no need to link me to some MDN pages for that.
I have a function to fetch user details and display it on the UI.
async function someHttpCall() {
throw 'someHttpCall error'
}
async function fetchUserDetails() {
throw 'fetchUserDetails error'
}
function displayUserDetails(userDetails) {
console.log('userDetails:', userDetails)
}
async function fetchUser() {
try {
const user = await someHttpCall()
try {
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (fetchUserDetailsError) {
console.log('fetching user error', fetchUserDetailsError)
}
} catch (someHttpCallError) {
console.log('networking error:', someHttpCallError)
}
}
It first makes HTTP call via someHttpCall and if it succeeds then it proceeds to fetchUserDetails and it that succeeds as well then we display the details on Ui via returndisplayUserDetails.
If someHttpCall failed, we will stop and not make fetchUserDetails call. In other words, we want to separate the error handling for someHttpCall and it’s data handling from fetchUserDetails
The function I wrote is with nested try catch blocks which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability using plain then and catch
This was my first atttempt
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
The problem with this is that the second then will run i.e. displayUserDetails even with someHttpCall failing. To avoid this I had to make the previous .catch blocks throw
so this is the updated version
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
However now the second catch will get called as a result of the throw. So when the someHttpCall failed, after we handled the someHttpCallError error, we would enter this block (fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) } which is not good since fetchUserDetails never gets called so we shouldn't need to handle fetchUserDetailsError (I know someHttpCallError became fetchUserDetailsError in this case)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal. So I am wondering how I can improve this by using .then and .catch to achieve the same goal here.
I am wondering how I can improve this by using .then and .catch to achieve the same goal here
You don't get to avoid the nesting if you want to replicate the same behaviour:
function fetchUser2() {
return someHttpCall().then(
(user) => {
return fetchUserDetails(user).then(
(details) => {
return displayUserDetails(details)
},
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
},
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
}
(The exact equivalent to try/catch would use .then(…).catch(…) instead of .then(…, …), but you might not actually want that.)
The function I wrote is [nested] which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability […]
For that, I would recommend to combine await with .catch():
async function fetchUser() {
try {
const user = await someHttpCall().catch(someHttpCallError => {
throw new Error('networking error', {cause: someHttpCallError});
});
const details = await fetchUserDetails(user).catch(fetchUserDetailsError => {
throw new Error('fetching user error', {cause: fetchUserDetailsError});
});
return displayUserDetails(details);
} catch (someError) {
console.log(someError.message, someError.cause);
}
}
(The cause option for Error is still quite new, you might need a polyfill for that)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
Actually, that sounds like an ideal situation. That means that you don't have to nest any try / catch blocks which could make you code a lot more readable. This is one of the things that async / await is meant to solve.
A solution could be is to create custom errors by extending the Error interface to be able to determine how and where the error occurs.
class CustomError extends Error {
constructor(name, ...args) {
super(...args)
this.name = name
}
}
Throw your errors within the functions that correspond with the error.
async function someHttpCall() {
throw new CustomError('HttpCallError', 'someHttpCall error');
}
async function fetchUserDetails(user) {
throw new CustomError('UserDetailsError', 'fetchUserDetails error')
}
Now you can control your error flow by checking the name property on the error to differentiate your errors.
async function fetchUser() {
try {
const user = await someHttpCall()
const details = await fetchUserDetails(user)
return displayUserDetails(details)
} catch (error) {
switch(error.name) {
case 'HttpCallError':
console.log('Networking error:', error)
break
case 'UserDetailsError':
console.log('Fetching user error', error)
break
}
}
}
I've been inspired by Rust's Result type (which forces you to handle every potential error along the way).
So what I do is handle exceptions in every individual function, and never allow one to throw, instead returning either an Error (if something went wrong) or the desired return value (if no exception occurred). Here's an example of how I do it (comments included):
TS Playground
If you aren't familiar with TypeScript, you can see the JavaScript-only version of the following code (with no type information) at the TypeScript Playground link above (on the right side of the page).
// This is the code in my exception-handling utility module:
// exception-utils.ts
export type Result <T = void, E extends Error = Error> = T | E;
export function getError (value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
export function isError <T>(value: T): value is T & Error {
return value instanceof Error;
}
export function assertNotError <T>(value: T): asserts value is Exclude<T, Error> {
if (value instanceof Error) throw value;
}
// This is how to use it:
// main.ts
import {assertNotError, getError, isError, type Result} from './exception-utils.ts';
/**
* Returns either Error or string ID,
* but won't throw because it catches exceptions internally
*/
declare function getStringFromAPI1 (): Promise<Result<string>>;
/**
* Requires ID from API1. Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*/
declare function getNumberFromAPI2 (id: string): Promise<Result<number>>;
/**
* Create version of second function with no parameter required:
* Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*
* The previous two functions work just like this, using the utilities
*/
async function fetchValueFromAPI2 (): Promise<Result<number>> {
try {
const id = await getStringFromAPI1(); // Error or string
assertNotError(id); // throws if `id` is an Error
return getNumberFromAPI2(id); // Error or number
}
catch (ex) {
return getError(ex);
}
}
async function doSomethingWithValueFromAPI2 (): Promise<void> {
const value = await fetchValueFromAPI2(); // value is number or Error
if (isError(value)) {
// handle error
}
else console.log(value); // value is number at this point
}

How to handle Firebase Firestore errors with async/await

I've been trying to figure out how to handle Firebase errors for the firestore using async/await. I want to show an error message, if a doc doesn't exist but I'm a little confused about how I would do that with async/await.
getResult.js
If I purposely give a fake document ID, the catch function automatically takes over and I can no longer access result.exists which is what I need in this case.
Is there a way I can easily check what type of error it is when I catch it, so I can return an appropriate message?
export const getResult = async (collection, doc) => {
try {
const resultRef = db.collection(collection);
const result = await resultRef.doc(doc).get();
if (result.exists) {
return { data: result };
} else {
return { data: "Error retrieving document, as it doesnt exist" };
}
} catch (err) {
if (err.response) {
console.log(err.response.data);
return err.response.data;
} else if (err.request) {
console.log(err.request);
return err.request;
} else {
console.log(err.message);
return err.message;
}
}
};
Is there a way I can easily check what type of error it is when I catch it?
The only piece of information you will get is the err object. We can't see what that is for your specific case, because we can't run your code. But it's what you'll have to use that object to figure out what the problem is and how to resolve it. These exception objects don't have a "type". You have to look at the contents of the object to understand what went wrong.
You might want to read up on exception handling in JavaScript. It is the same for all promises in JavaScript. Firestore is no different.

How to trigger React error boundaries when Promises are rejected? [duplicate]

I want to build a React-component which loads the data asynchronously on componentDidMount.
This is what the function currently looks like (written in TypeScript):
async componentDidMount(): Promise<void> {
try {
const { props: { teamId }, state: { } } = this;
const awaitableData = await UrlHelper.getDataAsync("some-fancy-url");
// ... do something with awaitableData
} catch(e) {
console.log("Some error occured");
throw e;
}
}
The render-function returns the markup wrapped in a ErrorBoundary component, which has componentDidCatch implemented.
However, this is never called/triggered when the awaited call is rejected and I end up in the catch-block.
What am I missing here?
async function is syntactic sugar for a regular function that returns a promise. An error in async function results in rejected promise. Even if rejected promise isn't handled anywhere and results in Uncaught (in promise) error, it is not caught by error boundaries.
As the reference states,
Error boundaries do not catch errors for: <...> Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
A solution is to change component state on error and handle it on next render. render is the place where error can be re-thrown synchronously.
An example:
state = { error: null };
async componentDidMount() {
try {
await new Promise(resolve => setTimeout(resolve, 2000));
throw new Error('Foo error');
} catch (error) {
this.setState({ error });
}
}
render() {
if (this.state.error) {
throw this.state.error;
}
return (
<p>Foo</p>
);
}
Using the above or below example with a functional component
provided a solution, something like this, for me:
const SomComponent: JSX.Element = () => {
const [state, setState] = useState({error: null, info: null})
... // something here setState the error
if (state.error) {
throw new Error('example') // Example throws to the error boundary
}
return <div> Some Component </div>
}
lets take a look to the Documentation
basically it says:
Error boundaries are React components that catch JavaScript errors
anywhere in their child component tree, log those errors, and display
a fallback UI instead of the component tree that crashed. Error
boundaries catch errors during rendering, in lifecycle methods, and in
constructors of the whole tree below them.
so basically when you are trying to use the ASYNC/AWAIT and it fails it will go out for the CATCH side of your function:
catch(e) {
console.log("Some error occured");
throw e;
}
and the error will not be thrown by the componentDidMount. actually if you remove the try catch method, the componentDidMount will take care of the error.

What is the best practice for handling async action errors in redux?

export const saveSomething = (thing = {}) => {
dispatch({
type: THING_SAVING,
});
return async function (dispatch) {
try {
await persistThing(thing);
} catch (e) {
dispatch({
type: THING_SAVE_ERROR,
error: e,
});
throw e;
}
dispatch({
type: THING_SAVED,
error: e,
});
}
}
handleSubmitClick(e) {
dispatch(saveSomething({ name: e.target.value }))
.catch(e => {
// pass
});
}
So, I'm probably overthinking this, but I'm trying to figure out the best practice here. When my async action has an error, even though I'm dispatching an error, it still feels right to throw it so the promise is rejected in case some components want to key on that. But, then that means in order to not have Create-React-App bomb on an unhandled promise rejection, I need to add this dummy catch when I dispatch the action, which IMHO is kinda ugly.
What's the best practice here? Not throw the error? Use the dummy catch? Something else?
Edit: The reason I need the dummy catch is because otherwise the app is bombing on me, at least in create-react-app:
(the error doesn't 100% match my pseudo code, but you get the point)
In my own projects I would try to manage all the error applications, probably with some generic action dispatcher that could be modified with proper error codes. But as for your example, you might not want to throw the error over, because you are using await in the method that can cause the error. So I would rewrite the code like this:
export const saveSomething = (thing = {}) => {
dispatch({
type: THING_SAVING,
});
return async function (dispatch) {
try {
await persistThing(thing);
dispatch({
type: THING_SAVED,
});
} catch (e) {
dispatch({
type: THING_SAVE_ERROR,
error: e,
});
}
}
}
If nothing happens and everything goes through the happy path, after the completion of the async call persistThing, the action dispatcher would get executed. And if persistThing generates an exception, the error action dispatcher would get executed.

Use ErrorBoundary together with asynchronous lifecycle-functions

I want to build a React-component which loads the data asynchronously on componentDidMount.
This is what the function currently looks like (written in TypeScript):
async componentDidMount(): Promise<void> {
try {
const { props: { teamId }, state: { } } = this;
const awaitableData = await UrlHelper.getDataAsync("some-fancy-url");
// ... do something with awaitableData
} catch(e) {
console.log("Some error occured");
throw e;
}
}
The render-function returns the markup wrapped in a ErrorBoundary component, which has componentDidCatch implemented.
However, this is never called/triggered when the awaited call is rejected and I end up in the catch-block.
What am I missing here?
async function is syntactic sugar for a regular function that returns a promise. An error in async function results in rejected promise. Even if rejected promise isn't handled anywhere and results in Uncaught (in promise) error, it is not caught by error boundaries.
As the reference states,
Error boundaries do not catch errors for: <...> Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
A solution is to change component state on error and handle it on next render. render is the place where error can be re-thrown synchronously.
An example:
state = { error: null };
async componentDidMount() {
try {
await new Promise(resolve => setTimeout(resolve, 2000));
throw new Error('Foo error');
} catch (error) {
this.setState({ error });
}
}
render() {
if (this.state.error) {
throw this.state.error;
}
return (
<p>Foo</p>
);
}
Using the above or below example with a functional component
provided a solution, something like this, for me:
const SomComponent: JSX.Element = () => {
const [state, setState] = useState({error: null, info: null})
... // something here setState the error
if (state.error) {
throw new Error('example') // Example throws to the error boundary
}
return <div> Some Component </div>
}
lets take a look to the Documentation
basically it says:
Error boundaries are React components that catch JavaScript errors
anywhere in their child component tree, log those errors, and display
a fallback UI instead of the component tree that crashed. Error
boundaries catch errors during rendering, in lifecycle methods, and in
constructors of the whole tree below them.
so basically when you are trying to use the ASYNC/AWAIT and it fails it will go out for the CATCH side of your function:
catch(e) {
console.log("Some error occured");
throw e;
}
and the error will not be thrown by the componentDidMount. actually if you remove the try catch method, the componentDidMount will take care of the error.

Categories

Resources