Coder Social home page Coder Social logo

grand_central's People

Contributors

ajjahn avatar jgaskins avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

ajjahn 7dir 1gor

grand_central's Issues

Handle async actions

Right now, all actions are handled synchronously. I've been thinking about ways to handle async actions for a while and @wied03 asked me about them, so I figured I'd open an issue to discuss them a bit more formally.

The idea I had was to add a special AsyncAction class so you could do something like this:

# The AsyncAction would signal the store to add then/fail handlers
FetchThing = GrandCentral::AsyncAction.with_attributes(:id) do
  # The promise method is what the then/fail handlers would
  def promise
    Browser::HTTP.get("/api/things/#{id}")
  end
end

# Typical synchronous LoadX action: convert a hash (i.e. from JSON) to the right kind of object
LoadThing = GrandCentral::Action.with_attributes(:hash) do
  def thing
    Thing.new(hash)
  end
end

store.dispatch FetchThing.new(thing_id) do |response|
  store.dispatch LoadThing.new(response.json[:thing])
end

Allow Store to take a stack of middlewares?

Inspired by Redux's middleware implementation, I think it would be pretty powerful to allow a Store to take a list of middleware to wrap around the reducer block.

I propose adding the following to the Store initializer:

module GrandCentral
  class Store
    def initialize initial_state, middlewares = [], &reducer
      @state = initial_state
      @reducer = middlewares.inject(reducer) do |stack, middleware|
        middleware.new(stack, self)
      end
      @dispatch_callbacks = []
    end
  end
end

That would allow you to do something like this:

APIRequest = Action.with_attributes :endpoint
APISuccess = Action.with_attributes :data
APIFailure = Action.with_attributes :error

class APIFetcher
  def initialize(reducer, store)
    @reducer = reducer
    @store = store
  end

  def call(state, action)
    fetch(action.endpoint) if APIRequest === action
    @reducer.call(state, action)
  end

  def fetch(url)
    Browser::HTTP.get(url).then { |data|
      @store.dispatch(APISuccess.new(data))
    }.fail { |error|
      @store.dispatch(APIFailure.new(error))
    }
  end
end

class DispatchLogger
  def initialize(reducer, store)
    @reducer = reducer
    @store = store
  end

  def call(state, action)
    puts action.class.name
    @reducer.call(state, action)
  end
end

Store = GrandCentral::Store.new(initial_state, [APIFetcher, DispatchLogger]) do |state, action|
  case action
  when APIRequest
    # Do something here if you want...
  when APISuccess
    state.merge(data: action.data)
  when APIFailure
    state.merge(error: action.error)
  else
    state
  end
end

Thoughts?

Rename VersionedStore#redo to VersionedStore#rollforward

Given "redo" is a control statement, would it make sense to rename VersionedStore#redo to VersionedStore#rollforward ? That way it's similar to "rollback". Or perhaps VersionedStore#rewind and VersionedStore#replay ?

Just an idea I'm tossing out there. I discovered grand_central recently and gave it a whirl and that caught me by surprise.

Make actions and models serializable

Given that current state is a reduction of the list of all actions performed on the initial state, making actions serializable could allow a store to send bug reports automatically. To do so, it could hold onto the initial state and action history, and when an exception is raised, it could be told to send a report to Bugsnag or another error-tracking service or even back to the app's home server.

Serializing actions would also make it possible to run the store and an entire Clearwater app on a web worker. The worker could take serialized actions from the UI thread using postMessage (which is not exposed in Bowser … yet), then the Clearwater app would generate the new vdom tree, diff against the previous tree, then pass the resulting patch back to the UI thread to make the changes to the DOM.

To make this possible, we would need to be able to convert arbitrary subclasses of GrandCentral::Action and GrandCentral::Model to and from native JS objects. In Opal, the conventions for this are Klass#to_n ("to native") and Klass.from_native(js_object).

For both classes, we control object initialization, so we know what gets passed in and what it's called. For example, with the following action:

Foo = GrandCentral::Action.with_attributes(:foo, :bar)

Then Foo.new(123, 'abc') can be serialized into { foo: 123, bar: "abc" }. We would have to have some additional metadata to be able to turn it back into a Foo action, though. Maybe { foo: 123, bar: "abc", __grand_central: { class: "Foo" } }? Once it's in the form of a native JS object, it's easily stringified to be able to send across the wire.

Same goes for a GrandCentral::Model:

class Bar < Model
  attributes(:id, :baz, :quux)
end

Bar.new(
  id: 123,
  baz: "abc",
  quux: "xyz",
).to_n
# {
#   id: 123,
#   baz: "abc",
#   quux: "xyz",
#   __grand_central: {
#     class: "Bar",
#   },
# }

I don't know what the CPU-time cost of all this is, but especially across a web worker it's probably significant; serializing large object graphs would mean recreating them on the other end (workers don't share memory with the UI thread). Either way, it'd be a good idea to measure.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.