Skip to content

Instantly share code, notes, and snippets.

@zfogg
Last active August 21, 2018 15:32
Show Gist options
  • Save zfogg/9efb1d0086fe06a6d932 to your computer and use it in GitHub Desktop.
Save zfogg/9efb1d0086fe06a6d932 to your computer and use it in GitHub Desktop.
Flux - brainstorming patterns for async actions

Flux - all about async actions

So, let's say you have a basic Flux action that fetches remote data asynchronously, and then dispatches that data to its store. The dispatch is called after the async fetch completes, using a promise or callback. Nothing out of the ordinary here! Just your standard async control flow patterns.

Next, let's say we take this action, tie it to a button's onClick, and then we click it. If the async call takes a bit of time to complete (as they tend to do), that click would feel like it either didn't go through or threw an error, because any components listening to the store would do absolutely nothing until the callback completes or the promise resolves (which is when the dispatch will occur).

UI interactions should always give feedback immediately.

If they don't, interactions feel unresponsive, and the app will feel 'broken' in many situations. It will be frustrating to use at the very least!


Flux says:

  1. an action represents an intent to make a discrete change to a store, and
  2. stores should properly reflect your app's state at all times.

In my opinion, the fact that state change will occur is non-trivial information; it's important app state! That is to say, the 'knowledge of itention' itself is information that its important to convey to the user.

Given my philosophies, and what we know about Flux, I think:

  1. actions should always dispatch synchronously
  2. only actions that make async calls, 'async actions', are permitted to call other actions
  3. successfully calling an async action should change the store at least twice
  4. successfully calling a sync action should change the store one or zero times
  5. handle async call completion and failure. always handle both cases, if you can
  6. an async action's disptach tell a store change into a 'transition state' (by setting its values to their defaults, or an isLoading flag to true, etc)
  7. transition states should be canceleable (async call doesn't fire callback, async call errors, etc)

Forget about laggy UI interactions.. how about buggy datastores that lie to you?

If you don't reset stores between async fetches, as you navigate around the app, old data will still be in the store while the fetch takes place, and components will be rendered with this old/expired state while the new fetch happens. This means that stores will send very wrong data to certain components, in a lot of cases.

For instance, let's say you:

  1. start at DocumentList
  2. edit Document1
  3. go back to DocumentList
  4. edit Document2

Unless the fetchDocument action resets its store while it's fetching, Document1 will still be in the store during initial component render.. so Document1 will be rendered on Document2's page for however long the fetch takes!! Fetch is REALLY quick on localhost, of course, so you won't notice this as you dev.. but it becomes a real problem in production, when requests take real time and do real work.


With login/logout systems, this can actually cause security holes, UI navigation errors, and other strange deeply-rooted bugs. And you likely won't notice these bugs as you dev, because they only become apparent as you navigate around the app naturally and make async calls that take more than a few milliseconds... while developing, we tend to be constantly refreshing/resetting browser/session state, and making requests to localhost that complete almost instantly, but that's not how users will experience your app in production at all.


Alt.js, a Flux implementation, addresses this, somewhat! Its solution (in its getting-started tutorial) is to combine two actions: it has you call a set action from within a fetch action.

To boil it down, Alt.js asserts that actions with async calls should work like this:

  1. component calls fetch action
  2. fetch action dispatches to fetch store listener 1. fetch store listener sets its store values to their defaults
  3. fetch action makes async call, passing a callback that will call set action
  4. fetch action returns, giving control back to the component that called it
  5. any components that are listening to store get rendered (with the default store values)

Some time later:

  1. the event loop comes across async call and executes it
  2. async call does work or gets data, and then fires off callback 2. callback calls set action
    1. set action dispatches the results of async call to set store listener
    2. set store listener sets its state according to the results of async call, and returns
    3. set action returns, giving control back to callback 2. callback returns, giving control back to async call
  3. async call returns, giving control back the event loop
  4. the event loop moves along, working through its messaging queue
  5. the event loop comes across any store listeners and executes them
  6. components that were listening for store changes update to match the new store state
  7. affected components get re-rendered (store state set according to the results of async call)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment