-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DevTools for Sagas #5
Comments
If I understand it's about 2 things
The first thing seems easy to implement. However we can't do that by wrapping effect creators (take, put, ...) because they are just pure functions so they are not aware of the context that created them (in test or in real code). Instead it can be added in the proc function (the one that drives the generator). We can for example make the middleware accepts an effectMonitor callback, the effectMonitor will be called each time an effect is executed like The second thing: I need to more understand the desired output: this control flow graph
By graph of saga control flow do you mean a call hierarchy of Sagas -> child Sagas/functions ? so we can for example log something like (
At first glance this seems doable, for example we can add a field which is the current call path. So now we can call the effectMonitor(currentCalPath, effect) so for the sample above it would give something like
And the log handler can transform this list into the desired control flow This is kind of a quick thinking. We need also to represent other kind of effects in the control flow model : paraellel effects, race, fork, join So to recap
|
Some inspiration: https://www.youtube.com/watch?v=Fo86aiBoomE |
Basically I was thinking about giving each saga an ID (internally) and notifying which saga is parent to which saga by embedding "parent ID" into every parent-child effect. So this would give us the call tree. |
let's see if I understood you correctly. I'll call the Sagas/effects tasks in the following When started, each task is giving an ID, and optionally the parent's ID of an already started task Each time a task is started I notify the monitor with something like monitor.taskcreated(id, parentId, taskDesc) and now we have a hierarchy of tasks Each time a task yields an effect I call the monitor.effectTriggered(taskId, effectId, effectDesc) so now the monitor can locate in which place in the call tree the effect was issued And for tasks that return results (usually promised functions) when the result is resolved I call monitor.effectResolved/Rejected(taskId, effectId, result) I saw the cerebral video and a couple of others. It seems the fact that the control flow being described declaratively helps a lot for tracing each operation. and with the state atom and cursor like operations every single mutation can be traced. In Redux the finest you can get is at the action level (which can leads to multiple mutations). More flexibility but less control. |
Hi guys! Just wanted to add an updated video on the Cerebral Debugger, which takes into account parallell requests, paths etc. Really glad it can inspire and really looking forward to see where this is going :-) https://www.youtube.com/watch?v=QhStJqngBXc |
Hello, When implementing devtools with Sagas, make sure that when you replay events the saga does not kick in and triggers new actions (replaying history should not modify that history and it could be easy to do so with sagas). |
@gaearon Can you please elaborate on this? Did I understand it correctly, that you would like to have an user interaction defined transaction boundary? Let's say onClick these actions have been dispatched:
|
#5 (comment) describes what I mean pretty well. Implementation wise I'd experiment with creating a store enhancer that would track the relevant DevTools state in a separate reducer, so those calls to monitor are just actions. See this approach in regular Redux DevTools. |
@gaearon in that case isn't this slight modification of const storeEnhancer = storeFactory => (reducer, initialState) => {
let correlationId = 0;
const store = storeFactory(reducer, initialState);
const wrappedDispatch = action => {
correlationId++;
if (typeof action === 'function') {
// The idea is to wrap dispatch only for thunks (which defines UI interaction transaction boundary)
return action(dispatchable => store.dispatch({...dispatchable, correlationId}), store.getState);
} else {
return store.dispatch({...action, correlationId});
}
};
return {
...store,
dispatch: wrappedDispatch
};
}; Given two action creators: const simpleActionCreator = () => ({type: 'SIMPLE'}); and const thunkActionCreator = (dispatch, getState) => {
dispatch({type: 'THUNK_STEP_1'});
dispatch({type: 'THUNK_STEP_2'});
setTimeout(() => {
dispatch({type: 'THUNK_STEP_3'});
}, 500);
}; when called sequentially dispatch(simpleActionCreator());
dispatch(thunkActionCreator); will dispatch these actions: [{type: 'SIMPLE', correlationId: 1},
{type: 'THUNK_STEP_1', correlationId: 2},
{type: 'THUNK_STEP_2', correlationId: 2},
{type: 'THUNK_STEP_3', correlationId: 2}] |
Because the implementation is exclusive with const storeEnhancer = storeFactory => (reducer, initialState) => {
let correlationId = 0;
const store = storeFactory(reducer, initialState);
const thunkMiddlewareWithCorrelationId = id => action => {
if (typeof action === 'function') {
return action(thunkMiddlewareWithCorrelationId(id), store.getState);
} else {
return store.dispatch({...action, correlationId: id});
}
};
const wrappedDispatch = action => {
correlationId++;
return thunkMiddlewareWithCorrelationId(correlationId)(action);
};
return {
...store,
dispatch: wrappedDispatch
};
}; EDIT: Reflecting the tree structure of correlation ids is fairly simple, you can display in devtools the exact async thunk hierarchy. Also the cool thing about this is that it replaces |
@gaearon I pushed a new branch for experimenting. This is not so big, but can serve as a starting point to refine the model Right now, the middleware can dispatch low level actions (defined here). usage example here This forms a kind of a DB with 2 tables: tasks and effects, with parent/child relation from tasks to effects, and a hierarchical parent/child relation on the tasks table itself. so we can construct different 'views' or 'queries' from this. Actually I can think of 2 different views
|
Reworked the monitoring branch. First, Sagas events are now dispatched as normal Redux actions, so you can handle them by a normal middleware and/or reducer. Second there are only 3 actions: There is an example of a saga monitor that watches all monitoring actions and update an internal tree. You can play with all examples (except real-world), by running the example and dispatching a for example
Below a sample snabpshot Another snapshot from the shopping-cart example |
waouuuh I like the way you display the race :) |
Happy you liked it! It took some tweaks but chrome console api is really awesome |
This is amazing stuff! I've recently started to look at Redux, didn't like redux-thunk, found redux-saga, started thinking about how to make sagas more explicit, and here we are! What I would love to see is a way to fully recreate the state of an app, not just the store/view, but also the implicit state of sagas. Is the idea here that eventually you could do this by replaying the event stream, and whenever a saga calls a function, automagically turn that into a take on the event representing the effect being resolved? |
I currently have an application that has all asynchronous logic in sagas. The problem I ran into is that the saga middleware spits out a huge amount of |
To be fair we already let you mute certain actions with https://github.com/zalmoxisus/redux-devtools-filter-actions. But I’m open to offering a built-in approach. |
Awesome Dan, I should have checked for a solution before posting. Cheers! |
@jfrolich redux-logger has a predicate in the config object to drop or print the given action: |
Great work so far! |
Some sagas appear as "unknown" in |
@yelouafi Would you be interested in releasing https://github.com/yelouafi/redux-saga/blob/master/examples/sagaMonitor/index.js as a separate module on npm? |
@davej Yes but unit tests are needed before making a separate release |
@pke (sorry for the late answer) Those are either sagas called directly without the declarative form |
Have you all seen https://github.com/skellock/reactotron ? |
@GantMan reactotron doesn't support sagas yet though, does it? |
I know the guy working on it, and he's planning on making it extendable to support a variety of items. We use Sagas all the time (BECAUSE IT'S AWESOME) so it's on the roadmap. As of now, just redux, but you can |
is there an example of using sagaMonitor with React Native? |
Enhanced the |
@sompylasar thanks, do you know how can I get the store or the dispatch function on chrome debug in React Native? |
@sibeliusseraphini sorry, I don't work with React Native, but I think Reactotron should help you with that – store and the dispatch function come from Redux itself, not Redux-Saga, so not related to the sagaMonitor. |
@sibeliusseraphini oh, and there is that: https://github.com/zalmoxisus/remote-redux-devtools |
I've just tried @yelouafi sagaMonitor example, it's really nice! however when I try to log my sagas I see a couple of unnamed parallel tasks. I think it's caused by my rootSaga (composed saga) and some of its children (composed as the parent one). There is a way to "label" composed sagas (yield [ ... ])? |
@pke @sompylasar I was getting "unknowns" as well. I traced it to "actionChannel", which isn't handled in |
Seems to me the hardest part about having dev tools for sagas is exposing the current state of the saga, rather than just the history of effects and what it is currently waiting on. For instance, observing my authentication saga I want to know what it thinks is the current user, not only that it is waiting on |
Could somebody please post a full-ish example of how to use the monitor? Or link to the documentation? It looks amazing but my best attempt at using it was unsuccessful. |
@emragins you can use the sagaMonitor defined in the redux-saga examples and pass it to createSagaMiddleware through the options object.
Now that the monitor is setup, you can call the logSaga from inside your code or easily the $$LogSagas from the console in your browser. @yelouafi maybe just two lines more in docs will help ;) |
Hello. I've seen that @yelouafi wrote an wonderful tool for logging Effects, and gave examples, but I have questions to that comment: There was attached pretty log like this: I've connect sagaMonitor to my project and saw not so pretty log like above So I've clone redux-saga-examples and run it, and... in console no logs at all. I'm doing something wrong? UPD. Yes, I thought that log in real time all my Effects, but it's not! Need to enter |
Sorry @DimonTD for that, was just a typo mistake. (Fixed for next readers...) |
@sibelius react native seems to run in a web worker when debugging in chrome https://corbt.com/posts/2015/12/19/debugging-with-global-variables-in-react-native.html you can access the global scope as mentioned in the article so all you have to do is the following somewhere in your code:
|
is this abandoned? is another solution for this? |
I think tools like Reactotron has some saga monitoring. There is also https://github.com/redux-saga/redux-saga-devtools , its graphical and working - but it needs polishing and more work, so its rather far from being finished and unfortunately noone is working on it at the moment. If you'd like to help, please reach out to me - project is quite straightforward and contributing to it shouldnt be too hard. |
I packaged redux-saga-devtools into a Chrome extension for folks to use |
Add a select() override that takes no arguments. Allow select(…) to work with generic selectors.
👍 for @Albert-Gao |
I guess this never happened, but it would be such a boon compared with sifting through enormous, nearly unreadable callstacks. |
I agree, at the very least, improving callstacks would be extremely helpful to the project. I'm going to raise this in the redux-saga in 2022 discussion. |
Redux DevTools currently leave a lot to be desired.
Mainly because they don't track async interactions at all.
It's impossible to say which component caused which async action, which in turn caused other async actions, which in turn dispatched some regular actions.
However sagas seem different. They make side effect control flow more explicit and don't execute side effects unless the caller demands so. If we wrapped top level functions such as take(), call(), put(), fork(), into tracking functions, we could potentially build a graph of saga control flow and trace action history through the sagas that generated it. This is similar to Cerebral debugger UI which unlike ours can trace which Signal was caused by which other Signal.
The developer wins in my opinion could be huge here. If this is possible to build we need to build it. At first as a proof of concept with console logging, and later with a UI, possibly as an extension to Redux DevTools.
What do you think? Am I making sense here? Have I missed something that makes this infeasible, not useful, or hard?
The text was updated successfully, but these errors were encountered: