redux-logic icon indicating copy to clipboard operation
redux-logic copied to clipboard

Feature Request: catchMiddleware request

Open kopax opened this issue 8 years ago • 4 comments

I need to dispatch a logout event when I receive a err.statusCode = 401.

So far, I have two solutions available:

  1. write this on each logic: inconvenient
  2. Pass the dispatch to my fetch wrapper function, then dispatch the action from: not the place to do it and also inconveniant

You recommended us in the documentation to use Observable to cancel an ajax call. Since fetch doesn't have a way to cancel an ajax request, and also can't be configured globally, I think it will be a nice feature if you add the possibility to catch globally the logic errors.

kopax avatar Mar 17 '17 13:03 kopax

How I did it is wrap all external REST API fetch calls in an RestApi class singleton, that is then passed as dependency to logics. So in typical logic I use it like so:

process({ action, api }, dispatch, done) {
  ...
  api.getSomething()
    .then(() => { successful result dispatch(...) })
    .catch(() => { some logging or other error action dispatch() })
    .then(() => done());
}

So api call specific error handling (validation errors or whatnot) goes here as normal.

And in the RestApi class I wrap fetch calls something like so:

class RestApi {
  constructor() {
    this.errors$ = new Rx.Subject(); // all fetch errors get written to this stream in addition to being returned as promise rejections
  },
  _fetchRequest(url, opts) {
    let status;
    return fetch(url, opts)
      .then(res => {
          status = res.status;
          if (status >= 400) {
               return Promise.reject(...i actually map stuff to specific custom error classes here)
          }
          return res.json();
      })
      .catch(err => {
          this.errors$.next(err); // here all errors, including the 401 are passed
          return Promise.reject(err);
      });
  },
  getSomething(..) {
    // prep req params and call
    return this._fetchRequest(...);
}}

const api = new RestApi();
export default api;

And now somewhere where store and dispatch are available, like your bootstrap script or main root view component you can listen to the errors$ stream, filter 401s and dispatch a specific app login action:

api
  .errors$
  .filter((err) => err instanceof AuthenticationRequiredError)
  .subscribe((err) => {
    log("Auth needed, redirecting to login.", err.message);
    dispatch(loginRequired());
  });

This way any REST API call anywhere in logics will trigger loginRequired on 401 without having to sprinkle this special error handling all over the place.

HTH.

erkiesken avatar Mar 17 '17 23:03 erkiesken

Hi @tehnomaag, I was waiting for @jeffbksi feedback on this but now I see he is not close to this repo anymore.

What is in your code new Rx.Subject(); ?

Could you elaborate a bit your example ? I am still facing the same issue.

kopax avatar Sep 07 '17 02:09 kopax

What is in your code new Rx.Subject(); ?

That's just an observable stream from RxJS project. RxJS is also what redux-logic is built upon internally. To get the minimum amount of dependencies import Subject directly, not the whole Rx namespace, ie:

import { Subject } from "rxjs/Subject";

erkiesken avatar Sep 07 '17 05:09 erkiesken

@kopax Sorry for the delay. I've been thinking through the next round of changes and how to accomplish. It's a balancing act of when to add new features and how to add them so that they don't cause confusion and complication.

Thanks for mentioning @tehnomaag yes, rxjs is a great way to enable this sort of thing.

They are working on an official mechanism for cancellation with fetch, so that is coming. In the meantime rxjs ajax supports this built-in and you can also do similar with axios with a little more wiring. As @tehnomaag mentioned it can also just be handled in your api functions.

jeffbski avatar Sep 07 '17 14:09 jeffbski