-
Notifications
You must be signed in to change notification settings - Fork 76
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
Asynchronous operations #29
Conversation
This looks like it would be a great addition. How about simplifying |
@rymohr 👍 thats much nicer |
@rymohr I've been pondering how to handle async actions, would love your thoughts Say you have a component that creates an action and you're interested in its progress (e.g. you create something on the server and you want to know when its done or error'ed). How do you handle this in a flux-ey way? var User = React.createClass({
saveUser: function () {
UserActionCreators.createUser({
name: "Foo"
});
}
}); One option would be to allow you to register callbacks for when an action status changes or make it into a promise
This kind of bothers me for two reasons
An alternative is each action creator returns an "action token". You would then have an Action store which allows you to get information about actions (e.g. their status)
It feels more in line with the flux architecture but unsure whether its a nice API for users. What do you think? |
That's a tricky one. After reading dozens of articles and interviews I'm not sure the facebook team has even found a great solution yet. I think the goal should be to encapsulate async operations into state as much as possible. If state includes data about pending requests, then there's no reason to add another way to monitor progress. The state will change as progress changes. I'd lean towards the second approach over the first one for sure. Views should not deal with async flows. I think I'd add a separate Whether that approach would scale to a production-size app is another question. At some point you'll have to clean up old actions to prevent the history from growing indefinitely and that adds yet another edge case to consider in views. |
Awesome, glad it makes sense 😄 What I'm now thinking is create an action store thats accessible at
{
"failed": true,
"type": "ADD_USER",
"pending": false,
"error": ...
} I completely agree that you shouldn't have to interact directly with var UserState = Marty.createStateMixin({
getState: function () {
return {
saveUser: Marty.getAction(this.state.saveUserToken)
};
}
});
var User = React.createClass({
mixins: [UserState],
render: function () {
if (saveUser.pending) {
// show loading gif
} else if (saveUser.error) {
// show error
} else if (saveUser.done) {
// show something else
}
},
saveUser: function () {
this.setState({
saveUserToken: UserActionCreators.createUser({
name: "Foo"
})
});
}
}); Removing actions from the store is a really good point and I'm not sure what the answer is. Perhaps its worth implementing a naive time based strategy (e.g. remove anything thats not been used in > 5 minutes) but provide the opportunity to implement your own strategy |
Looks good! Here's some alternate naming strategies to consider: Before / after
OptimisticThis is the default behavior of react-flux
Promise-like
Short
I don't have a strong preference, but I slightly lean towards the short form because:
Whichever you choose to go with, I really like how react-flux provides a helper for defining constants to encourage a consistent naming scheme: https://github.com/kjda/ReactFlux#constants |
I hadn't seen https://github.com/kjda/ReactFlux#constants before, really like that. agree on the short form, would be nice to keep a consistent terminology throughout |
…ected behaviour. Fix various little bugs
Looks really good! |
Stores
Adding a new function to the store called
query
which is responsible forStoreQuery
that represents the lifecycle of the queryStoreQuery
has astatus
which can be pending, error or done. Like stores, there is anaddChangeListener
which allows you to register a callback when the status of the query changes.If the
StoreQuery
fails, you can see why by accessingquery.error
. If the query is successful, the result is accessible by accessingquery.result
.Only one query per key can be pending at any one time. While a query for a given key is in progress, invoking
Store#query
with that key will return the in progress query. This is to avoid making multiple queries unnecessarily. The key is unlocked once its associated query has completed successfully or failed.If the local query returns something other than null or undefined then the
StoreQuery
moves to the successful state and the result is available inStoreQuery#result
. If an error is thrown within the local query, theStoreQuery
moves to the failed state and the thrown error is available inStoreQuery#error
If the local query returns null or undefined then the remote query is executed. Once the remote query completes, the local query is re-executed. If the local query now returns something other that null or undefined then the
StoreQuery
moves to the successful state and the result is available inStoreQuery#result
. If the local query returns null or undefined then theStoreQuery
moves to the failed state andStoreQuery#error
will return the error "Not found". If an error is thrown within the remote query, theStoreQuery
moves to the failed state and the thrown error is available inStoreQuery#error
.If the remote query returns a promise then the completion of the remote query will be delayed until the promise has resolved.
If you are using a state mixin and the state contains a
StoreQuery
, the mixin will automatically add a change listener and update the components when the query status changes.Action creators
Actions are responsible for coordinating state mutations. Actions can only mutate local state but have the potential to mutate remote state. It is also common to do an optimistic local mutation and then deal with aliures later on.
An actions type is defined by
ActionCreator#getActionType(functionName)
. The default strategy is to upper case and separate using underscores, sosaveUser
becomesSAVE_USER
. You can overridegetActionType
or choose a specific action type by specifying it as the first element of an arrayEvery action creator function will return an action token. You can pass this token to
Marty.getAction(actionToken)
to get the status of an action. The state mixin automatically listens to the action store (unless you setlistenToActions: false
) but will only update the views state if you return an action fromgetState
which is the action that just changedYou can listen for changes to actions by registering handlers for
ACTION_STARTING
,ACTION_ERROR
andACTION_DONE
.If you return a promise from an action creator, the action will wait for the promise to complete before updating the action status.
To do
Store#query