Dynalon / reactive-state

Redux-clone build with strict typing and RxJS down to its core. Wrist-friendly, no boilerplate or endless switch statements
MIT License
138 stars 7 forks source link

Error handling best practice #21

Closed esbenjuul closed 4 years ago

esbenjuul commented 4 years ago

Hi I don't know if this is the right place to reach out for help? I am currently using reactive-state in a semi large application and so far everything works great. But I am a little bit unsure how to implement a proper error handling in my network requests. In your wiki you're do not recommend using subscribe on RxJs streams but I can't really see how to do it different if I want to handle errors in a general way. I would like to disable my stream if it is an error and not complete the 'fetchPostEffect' if it's a network error but currently everything runs through.

Do you have any best practice how to handle side-effects when errors occurs. Example below is my current approach but it doesn't feel right.

ex.

const handleError = new Subject();
store.addReducer(handleError, (state, payload) => ({ ...state, error: payload.message }));

export const fetchPosts = new Subject();
const fetchPostEffect = fetchPosts.pipe(
  switchMap(() => fetch('http://httpstat.us/500')),
  switchMap((response) => {
    if (response.ok) {
      // OK return data
      return response.json();
    }
    return of({ error: true, message: `Error ${response.status}` });
  }),
  catchError((err) => {
    return of({ error: true, message: err.message });
    }),
);

const generalErrorProxy = (payload) => {
  if (payload.error) {
    handleError.next(payload);
    return null;
  }
  return payload;
};

// catch network errors
fetchPostEffect.subscribe(generalErrorProxy);

store.addReducer(fetchPostEffect, (state, payload) => ({ ...state, posts: payload }));

store.watch((state) => state.posts).subscribe((n) => console.log('watch', n));
store.watch((state) => state.error).subscribe((n) => console.log('error', n));
store.watch().subscribe((n) => console.log('state', n));`

// this cycle returns:
// 'error', {...}
// 'state', {...}
// 'watch', {...}
// 'state', {...}

Thanks in advance. Best Esben

Dynalon commented 4 years ago

In your wiki you're do not recommend using subscribe on RxJs streams

There is a misunderstanding here - it is fine to subscribe to RxJS streams (observables). I am just discouraging directly subscribing to a Subject that is used as an action:

const handleError = new Subject();
const sideEffect = new Subject();

// discouraged, do not use
someObservable.subscribe(handleError)

// OK: always pass a function to subscribe to values
someObservable.subscribe(n => effect.next(n))
// OK: subscribe to errors from an observable
someObservable.subscribe(undefined, (err) => handleError.next(err))

Here is the reasoning against: someObservable.subscribe(handleError) - If someObservable completes or errors, the stream is closed on the handleError subject too. This means you can't dispatch any other errors or values to that subject.

esbenjuul commented 4 years ago

Hi @Dynalon Thanks for your fast reply. I make sense! I tried to find some RxJs pattern that would fit into the fetch promise and the reactive-state and for some reasons it a bit difficult to find best practices. Would you recommend to handle errors like this or is there a better way in your opinion?

export const handleError = new Subject();
store.addReducer(handleError, (state, payload) => ({ ...state, error: payload.message }));

export const fetchPosts = new Subject();
const savePosts = new Subject();

const fetchPostEffect = fetchPosts.pipe(
  switchMap(() => fetch('http://httpstat.us/500')),
  switchMap((response) => {
    if (response.ok) {
      // OK return data
      return response.json();
    }
    // return 4xx errors
    return throw Error({ error: true, message: `Error ${response.status}` });
  }),
  catchError((err) => {
     // return 5xx errors
     throw new Error({ error: true, message: err.message });
  }),
);

// catch response
fetchPostEffect.subscribe(result => savePost.next(result), error => handleError.next(error));
store.addReducer(savePosts, (state, payload) => ({ ...state, posts: payload }));
Dynalon commented 4 years ago

I'm unsure what is "the best way" (TM) to do it, it highly depends on the requirements. I think both your approaches are valid and do the job.

Closing this as it not a fixable bug in reactive-state.

esbenjuul commented 4 years ago

Hi @Dynalon Thanks for your reply and great work by the way.

Best Esben