redux-observable: Reducer errors swallowed silently after epics' outputs
const reducer = (state = {}, action) => {
switch (action.type) {
case 'THING':
return { ...state, thing: action.nonexistent.childprop }
default:
return state;
}
};
export function sampleEpic(action$: Observable, store: Store) {
return action$.ofType(ANYTHING)
.concatMap(action =>
fetch('/api/anything', { ... })
.mergeMap(response => {
switch (response.status) {
case 409:
return Observable.of(pleaseNo(email));
case 201:
return Observable.of({ type: 'THING' }, { type: 'NEVER_DISPATCHED' });
default:
throw new Error(`Unknown response code ${response.code}`);
}
});
}
The THING action causes a crash that is hidden from the user (me for the last 2 hours) so that it’s very hard to see where things went wrong.
Adding catch to the end of the epic doesn’t make a difference.
All middleware a successfully run.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 7
- Comments: 21 (14 by maintainers)
Commits related to this issue
- #263 catch and console exception from reducers — committed to Jerry-Hong/redux-observable by deleted user 7 years ago
- fix(error swallowed): always log reducers exception Closes #263 — committed to Jerry-Hong/redux-observable by deleted user 7 years ago
- fix(errors): errors from reducers are no longer caught and logged, instead are rethrown. related #263#issuecomment-395109222 BREAKING CHANGE: For 0.19.0 errors from reducers are no longer caught and ... — committed to redux-observable/redux-observable by jayphelps 6 years ago
released discussed change as redux-observable@0.19.0
Technically we could remove the error catching entirely because the fix I did upstream in rxjs https://github.com/ReactiveX/rxjs/blob/stable/CHANGELOG.md#556-2017-12-21 fixed the actual swallowing issue, the console.error was just a workaround. The only issue with removing it is that anyone who has not yet updated their rxjs version high enough to pick up the fix will have swallowed errors again. But perhaps that’s indeed the correct solution, advising people to upgrade their rxjs instead.
Reopening. We merged a workaround where we logged all errors from reducers but I’m concerned about it or any other similar solution. See the discussion in #379.
@maxkostow whoops you’re right, I just fixed my example. Thanks! 😄
If you apply
catchlikecombineEpics(...epics)(...args).catch(...)it’s too late–all your other epics have already been unsubscribed so while you could restart them (by returning the source observable which is the second argument) they would have lost any local state. Usually that’s not a big deal because the only state in your epics themselves should be transient state; like the statedebounceTimemaintains, etc. All else should be in the redux store.If instead you want to protect each of them from terminating the others, you can call each epic individually and add
catchon each resulting stream.Here’s an example of that, but it may be hard to grok since it uses a lot of function composition:
Now if any individual epic errors out, we’ll log the error message and restart it, but the other epics will continue with no knowledge that it happened and maintain any state they had.
We’ve talked about whether this (or something else) should actually be how it works by default in #94, but haven’t reached a solid consensus yet. But we should at least document these patterns probably… 😞
There are potential issues with restarting epics after they error out, if the devs aren’t strictly aware of the implications. e.g. what if the redux store is in a different state than the epic expects? Does the epic emit anything at start up? What if the error happens infinitely so it’s erroring and restarting synchronously, locking up the browser? etc
EDIT: this particular situation no longer gets swallowed, but some other cases still do.
Minimum reproduction:
https://jsbin.com/dukixu/edit?html,js,output
Also appears related to Subject usage (but could be a red herring)