{"action":"created","issue":{"url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/7","repository_url":"https://api.github.com/repos/zewa666/aurelia-store-examples","labels_url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/7/labels{/name}","comments_url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/7/comments","events_url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/7/events","html_url":"https://github.com/zewa666/aurelia-store-examples/issues/7","id":377207991,"node_id":"MDU6SXNzdWUzNzcyMDc5OTE=","number":7,"title":"What types of logic belong inside actions?","user":{"login":"mipatterson","id":1103769,"node_id":"MDQ6VXNlcjExMDM3Njk=","avatar_url":"https://avatars0.githubusercontent.com/u/1103769?v=4","gravatar_id":"","url":"https://api.github.com/users/mipatterson","html_url":"https://github.com/mipatterson","followers_url":"https://api.github.com/users/mipatterson/followers","following_url":"https://api.github.com/users/mipatterson/following{/other_user}","gists_url":"https://api.github.com/users/mipatterson/gists{/gist_id}","starred_url":"https://api.github.com/users/mipatterson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mipatterson/subscriptions","organizations_url":"https://api.github.com/users/mipatterson/orgs","repos_url":"https://api.github.com/users/mipatterson/repos","events_url":"https://api.github.com/users/mipatterson/events{/privacy}","received_events_url":"https://api.github.com/users/mipatterson/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2018-11-04T23:32:45Z","updated_at":"2018-11-05T06:56:58Z","closed_at":null,"author_association":"NONE","body":"I have a question about what types of logic belong within actions.\r\n\r\nIn the services example, the HTTP call to save a new developer is made and awaited within the addDeveloper action and when the promise resolves, a new state is returned and the action is completed.\r\n\r\n```\r\nexport async function addDeveloper(state: DeveloperState, category: DeveloperCategory, name: string, skills: string[]) {\r\n  if (!category || !name || !skills) {\r\n    return state;\r\n  }\r\n\r\n  const newState = Object.assign({}, state);\r\n\r\n  // we await the Promise returned by the service method\r\n  const res = await developerService.addNewDeveloper(category, name, skills);\r\n\r\n  // depending on whether the currently active category is all or the one matching the new dev we'll add the new dev as well\r\n  if (newState.activeCategory === category || newState.activeCategory === \"all\") {\r\n    newState.developers = [ ...state.developers, { ...res }];\r\n  }\r\n\r\n  return newState;\r\n}\r\n```\r\n\r\nAt first, this seemed fine to me, until I realized that until the HTTP call promise resolves, no other state changes can be made, because the addDeveloper action will block any other actions from being handled. (They can be queued, but not actually handled.) So if the HTTP call to persist the new developer data takes a long time because of high load on the server, and during that time another unrelated action is dispatched, that unrelated action will not be handled right away. In some cases, this might not be a big deal, but it has the potential to create a poor user experience depending on what that queued action was supposed to do and what the consequences are of it being delayed.\r\n\r\nBecause of the way actions are queued and handled, it seems to me that all actions should be as fast as possible and that server calls should almost never be placed inside actions. For example, I would rewrite the above action as a series of actions, wrapped in a separate method:\r\n\r\n```\r\nfunction startSavingDeveloperAction(state: DeveloperState): DeveloperState {\r\n  return {\r\n    ...state,\r\n    savingDeveloper: true,\r\n  };\r\n}\r\n\r\nfunction completeSavingDeveloperAction(state: DeveloperState, newDeveloper: any): DeveloperState {\r\n  return {\r\n\t...state,\r\n\tsavingDeveloper: false,\r\n\tdevelopers: [...state.developers, newDeveloper],\r\n  };\r\n}\r\n\r\nexport async function addDeveloper(store: Store<DeveloperState>, category: DeveloperCategory, name: string, skills: string[]) {\r\n  // Validate\r\n  if (!category || !name || !skills) {\r\n    return;\r\n  }\r\n\r\n  // Dispatch action to update flag in state to indicate we are saving a new developer\r\n  await store.dispatch(\"startSavingDeveloperAction\");\r\n\r\n  // Save the new developer via service HTTP call\r\n  const res = await developerService.addNewDeveloper(category, name, skills);\r\n\r\n  // Dispatch action to add new developer to state\r\n  await store.dispatch(\"completeSavingDeveloperAction\");\r\n}\r\n```\r\n\r\nThe above code takes more of a redux-thunk style approach to asynchronous actions, keeping the actions small and quick and chaining them together inside another function that has the ability to dispatch actions. I can definitely see the potential need for calling asynchronous code from within an aurelia-store action (perhaps the need to call into a third-party library), but it seems to me that in many scenarios this is not the right approach.\r\n\r\nCould you clarify in which scenarios making actions asynchronous is the correct approach and what types of logic should be inside actions? Perhaps I am misunderstanding how aurelia-store handles these scenarios.\r\n\r\nThanks!"},"comment":{"url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/comments/435772598","html_url":"https://github.com/zewa666/aurelia-store-examples/issues/7#issuecomment-435772598","issue_url":"https://api.github.com/repos/zewa666/aurelia-store-examples/issues/7","id":435772598,"node_id":"MDEyOklzc3VlQ29tbWVudDQzNTc3MjU5OA==","user":{"login":"zewa666","id":2861414,"node_id":"MDQ6VXNlcjI4NjE0MTQ=","avatar_url":"https://avatars0.githubusercontent.com/u/2861414?v=4","gravatar_id":"","url":"https://api.github.com/users/zewa666","html_url":"https://github.com/zewa666","followers_url":"https://api.github.com/users/zewa666/followers","following_url":"https://api.github.com/users/zewa666/following{/other_user}","gists_url":"https://api.github.com/users/zewa666/gists{/gist_id}","starred_url":"https://api.github.com/users/zewa666/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zewa666/subscriptions","organizations_url":"https://api.github.com/users/zewa666/orgs","repos_url":"https://api.github.com/users/zewa666/repos","events_url":"https://api.github.com/users/zewa666/events{/privacy}","received_events_url":"https://api.github.com/users/zewa666/received_events","type":"User","site_admin":false},"created_at":"2018-11-05T06:56:58Z","updated_at":"2018-11-05T06:56:58Z","author_association":"OWNER","body":"@mipatterson excellent question, which is currently a lot under discussion. I would sum up and say, yes you're approach is better as it's more defensive. As @Vheissu explained if you keep the requests minimal with fast respones, you'll likely not notice anything. But working with external service, potentially cross-continents, it's easily possible you'll hit some high latency issues.\r\n\r\nSo in general if you want to be on the safe side, having a `starting` and `ending` action (splitting up the request and response-handling) is definitely the way to go. There is just no right or wrong per se as it heavily depends on the use-case, sometimes there are things you explicitely need to wait for and other times you just want to keep the UI `responsive`, but I think you summed it up pretty well.\r\n\r\nWould you like to extend the service sample with another feature, e.g. deleting a developer, showing off your approach? In the comments you could address the difference from the `addDeveloper` approach, so we have both documented."}}
{"actor":{"display_login":"zewa666"}}