Reducer error cancelling all Tasks, killing all sagas - redux

Twice recently I've encountered an error in a reducer due to an undefined variable.
In both cases this resulted in the application becoming unresponsive because the error had a cascading effect and all my saga "take" commands were cancelled.
e.g.
Tasks cancelled due to error:
takeEvery(actionName1, sl)
takeLatest(actionName2, Yi)
etc.
Also, in both cases the fix was simple - to add defensive coding to the reducer to prevent this.
However, the effect is devastating on the app if the code allows an undefined to slip through and I'm wondering if there is another way?
Should I wrap the switch in the reducer with a try/catch?
Is there a
way to stop all the tasks cancelling when an error occurs?
Thanks for any input

I have an answer that works but I would still appreciate comments to validate if this is the best answer as I'm relatively new to Redux.
I implemented my first suggestion from above and it seems to work well and would stop my problem
try {
switch (action.type) {
case '...':
...
} catch (err) {
// Do something with the error here
return state; // state is unchanged
}
Is this correct or is there a better way?
EDIT: This needs to be a pure component so "Do something with the error here" has to mean add something to state to track the error. Anything else needs to be done by something acting on that state change.

Related

Fluxor: An effect which doesn't dispatch an action?

I'm using Fluxor to manage state in a Blazor wasm app.
I have the following effect which is triggered after getting a result from deleting an item:
[EffectMethod]
public Task HandleDeleteBudgetResultAction(DeleteBudgetResultAction action, IDispatcher dispatcher)
{
if (action.Success)
{
NavigationManager.NavigateTo("/budgets", false);
}
return Task.CompletedTask;
}
Essentially, if the delete was successful, navigate back to the list page. If it wasn't, do nothing as we need to remain on the detail page.
In this scenario I do not need to dispatch an action, but I have to include the dispatcher parameter, as demanded by the EffectMethod attribute. And since I have no async processes in this method, I am returning Task.CompletedTask.
This obviously feels wrong, so my question is: is this a limitation of Fluxor, or have I architected the flow incorrectly? As far as I'm aware, an effect doesn't have to dispatch an action.
I was thinking I might need to move my navigation state into the store, but I think I'll just come across the same problem again because I'll still need to call NavigationManager from somewhere.
Any help or better solutions appreciated :)
It is a limitation of Fluxor. You can dispatch a GoAction instead of injecting the NavigationManager, as long as you have called UseRouting on the Fluxor options.
builder.Services.AddFluxor(options => options
.UseRouting()
.ScanAssemblies(typeof(Program).Assembly)

redux-injectors: Using yield select in saga before reducer and saga are injected

Hello this is my first question. I am trying to set up a project where modules along with the redux and sagas will be injected into the main app, using redux-injectors. In my sagas I want to use yield select, to check if an action has updated the state and then carry on. For example, when I post an image, I want to make sure there were no errors in posting the file and then move on. I use the following function:
export const imageErrors = (state: RootState): IImagesErrorState => state.image.errors
and then in the saga.ts file I use it as such:
if (imagesErrors?.postImageError !== null) {
throw imagesErrors.postImageError
}
this works fine as long as the state.image exists in the root state from the beginning. However, how do I do that when I want to inject this state later on using useInjectReducer and useInjectSaga? I obviously get an error
Property 'image' does not exist on type 'Reducer<CombinedState<{ user: CombinedState<{ auth: IAuthState; errors: IErrorState; }>; }>, AnyAction>'.ts(2339)
So how do we handle selectors of specific pieces of state, since state does not yet include them?
Thank you so much.
Can't talk about the Typescript part of things, but in terms of architecture you've got two options.
One is the obvious - that is to add conditions or ? everywhere to avoid errors from accessing missing properties, but that can get tedious quickly.
The other probably better option is to rethink your state & application chunks. What is this saga that is accessing state that isn't existing yet? Does it need to run before you have such state? If not, let's move the saga to the same chunk as the reducer. In the opposite case, where you need the saga to be running e.g. as part of the runtime chunk, then perhaps the image state should be in the runtime chunk as well.

Force iron-router to get back an ready from waitOn

Currently it seems not to be possible to force a ready() state in the route. For example:
I have a waitOn on 2 subscribtions. One of them returns a Meteor.Error - now the route will be in the loading-state with no ending.
Is there a recommend way to tell iron-router "waitOn until subscribtion is ready OR subscribtion fails with an error" ?
Edit:
To explain my special case:
The waitOn is for a route which is for searching. The search arguments are "what" and "where". In "where" I have a plan String Address and need to convert it to a geo coordinate. For this I use the googlemaps converter on the Serverside (because its Sync). When no address was found I need to get back a error a lá "This address must be wrong". For this I need the functionality to get back an error.
When I do it like David Weldon said I need to do this step in the waitOn method but the Client-Side googlemaps converter is not Sync - instead its async so this would not work.
General Recommendations
It's okay for your publishers to throw errors, but those conditions should only be hit if the client does the wrong thing. In other words, you are solving the wrong problem - you should only subscribe when you know the publisher will not throw an error. Let's look at an example:
Suppose your route needs to subscribe to newPosts and postsForSuperuser. Assume that the postsForSuperuser publisher will throw an error if the user isn't a superuser. It's now the client's job not to let that happen. The waiton definition could look like:
waitOn: function() {
var subs = [Meteor.subscribe('newPosts')];
if (Roles.userIsInRole(Meteor.user(), ['superuser']))
subs.push(Meteor.subscribe('postsForSuperuser'));
return subs;
}
Because we are conditionally adding the postsForSuperuser subscription, we don't give the publisher the opportunity to throw an error.
Your specific use case
You case is a little more tricky, because mechanically the client is doing the correct thing but the user input may happen to be bad. In this case, I don't think throwing an error is appropriate. Here are some recommendations:
Avoid the problem by checking the address via a method call prior to changing the route.
If an address is found to be invalid, have the publish function immediately return this.ready(). This will prevent your route from failing, but you'll be left assuming that the reason you have no data is because of the address. If that's a valid assumption (i.e. it's the only possible reason for failure), then your router could deal with this by using a dataNotFound hook.
If you need to explicitly identify the cause of the error, have a close look at the 'counts' example from the docs. You can declare a client-only collection called addressErrors and then call this.added with a dynamically created document describing the cause of the error. The implementation of this is a little more tricky, and probably worthy of a separate question if you get stuck. I'd see if the first two make sense before attempting it.

How to debug slow meteor methods?

A number of my meteor methods have mysteriously slowed down recently. Whereas they used to be quite snappy, many are taking 10 or so seconds.
Things not causing the slowdown:
Additional functionality, the slowed sections of the codebase haven't been changed significantly
Machine load (cpu load hovering around 30%)
Additional DB load (no new queries added)
transfer time of the return data (returns undefined)
blocking method (I've tried with this.unblock() within the method)
I did debugging by using console.time() / console.timeEnd() on both the server and client side. The server side code takes about .3 seconds to run, but the client doesnt get the callback until about 11 seconds after the meteor.call() ...
This is the server method:
function cancelSomething(somethingId, reason) {
console.time('cancelSomething method');
check(somethingId, String);
check(reason, String);
if (!AuthChecks()))
throw new Meteor.Error(401, 'Not Authorized');
var something = MySomethings.findOne({'_id': somethingId});
if (!something)
throw new Meteor.Error(404, 'Something not found');
var returnVal = SomethingService.cancel(something, reason);
console.timeEnd('cancelSomething method'); // <--- prints "cancelSomething 350ms" or there abouts
return returnVal;
}
clientSide:
console.time('meteorCall');
Meteor.call('cancelSomething', this._id, reason, function(err) {
if (err) {
console.log('an error occurred', err);
}
console.timeEnd('meteorCall'); // <--- prints "meteorCall 11500" or so
});
EDIT:
I have noticed there is some correlation with the quantity of docs within the "somethings" in the db. w/ 500 documents it takes about 1 second to receive the return on the client, with 5000 it takes about 8.5 seconds ...
Interesting. I think this one is hard to answer without knowing more about your app, but I have some suggestions that could help steer you in the right direction.
blocking
If we can assume that the timers are working properly, what could be happening is that the server is unable to begin execution of the method because another method call by the same client is already in progress. Have a look at unblock in the docs. The answer may be as simple as putting a this.unblock at the top of one of your methods.
syncing
If SomethingService.cancel does something which results in a lot of DDP traffic, that could tie up the client for a while and make it unable to execute your callback. For example if it:
modified or created a lot of documents which then had to be synced to the client
indirectly caused an expensive subscription to be rerun (my money is on this one)
Updated Answer
It seems the problem had to to with a call to observe, which makes a lot of sense given your high CPU load. Frankly, it's a little silly that I didn't suggest this because I have already answered a similar question here. If you want to try to keep the observe, here are some additional suggestions:
Use oplog tailing.
Try to narrow the scope of the observe as much as possible using selectors which are currently supported by the oplog observe driver. The supported selectors should improve at or before meteor 1.0.
Limit the fields to only those you need.
Check that oplog tailing is working by adding the facts package.

Why does array_map throw a warning when the closure raises an exception?

I've recently started programming with PHP again, after a long stint with other languages during which i've developed a more functional style - which i'm hoping to try and maintain.
I've noticed some weird behaviour, which I managed to distill into a testcase that I'm hoping someone can explain.
$func = function($item) {
if ($item == 0)
throw new Exception("Can't do 0");
return $item;
};
try {
array_map($func, array(1, 2, 3, 0, 5));
} catch (Exception $ex) {
echo "Couldn't map array";
}
When executing the above code, i see the following output:
Warning: array_map(): An error occurred while invoking the map callback in map_closure.php on line 10
Couldn't map array
I can suppress the error with # on array_map, but this seems hacky at best.
The warning is generated because, put simply, the callback function is not returning normally (due to throwing the Exception). This is just the way that array_map() is coded, if the callback function does not complete its execution. Remember an Exception breaks out of execution immediately, as far as your PHP code is concerned.
As for how to silence the warning, that's entirely up to you. Unfortunately, the warning will be generated and it's your choice to bury it or let it get displayed.
As an aside, maybe your test case was over-simplified but, it would make much more sense to use array_filter() (or perhaps array_reduce()) there.
As preinhaimer says, array_map makes it really hard for you to see exactly what happened during its execution because it predates exceptions. It would not be practical to change its behavior anymore since that would lead to lots of (poorly-coded) applications breaking; that's life.
If you want a mechanism with which to check if the array_map completed without errors or not, I have posted a detailed answer (with code) to this question which deals with practically the same problem. It's not as easy as try/catch, but you work with what you have.
Either use # or a foreach instead of array_map
array_map() predates exceptions so it still uses warnings. There's a few annoying places in PHP where you're still forced to use error handling, this is one of them.
You're left with options like having it return null or some other un-used value when it encounters a problem, or filtering the array to ensure it only contains valid options before you run it through array_map.

Resources