Skip to content

Instantly share code, notes, and snippets.

@catmando
Created January 17, 2017 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save catmando/6fae8e6fdb3d2d11f2e3f46bb5beb580 to your computer and use it in GitHub Desktop.
Save catmando/6fae8e6fdb3d2d11f2e3f46bb5beb580 to your computer and use it in GitHub Desktop.
module HyperStore
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def state_reader(state_name)
define_method(state_name) { React::State.get_state(self, state_name) }
end
end
# render {}
def state
@state_wrapper ||= React::StateWrapper.new(self.class, self)
end
def loaded?
[0, nil].include? React::State.get_state(self, '__internal_loading_count__')
end
class Base
def self.inherited(child)
child.include(HyperStore)
end
end
end
module React
class StateWrapper < BasicObject
def method_missing(method, *args)
if match = method.match(/^(.+)\!$/)
if args.count > 0
current_value = State.get_state(@from, match[1])
if args[0].is_a? Promise
state_name = $1
State.set_state(@from, '__internal_loading_count__', (State.get_state(@from, '__internal_loading_count__') || 0)+1)
args[0].then do |result|
State.set_state(@from, state_name, result)
State.set_state(@from, '__internal_loading_count__', State.get_state(@from, '__internal_loading_count__')-1)
end
else
State.set_state(@from, $1, args[0])
end
current_value
else
current_state = State.get_state(@from, match[1])
State.set_state(@from, $1, current_state)
Observable.new(current_state) do |update|
State.set_state(@from, $1, update)
end
end
else
State.get_state(@from, method)
end
end
end
end
# if you are using < Opal 0.10 you need the updated promises class too:
class Promise
def self.value(value)
new.resolve(value)
end
def self.error(value)
new.reject(value)
end
def self.when(*promises)
When.new(promises)
end
attr_reader :error, :prev, :next
def initialize(action = {})
@action = action
@realized = false
@exception = false
@value = nil
@error = nil
@delayed = false
@prev = nil
@next = []
end
def value
if Promise === @value
@value.value
else
@value
end
end
def act?
@action.has_key?(:success) || @action.has_key?(:always)
end
def action
@action.keys
end
def exception?
@exception
end
def realized?
!!@realized
end
def resolved?
@realized == :resolve
end
def rejected?
@realized == :reject
end
def ^(promise)
promise << self
self >> promise
promise
end
def <<(promise)
@prev = promise
self
end
def >>(promise)
@next << promise
if exception?
promise.reject(@delayed[0])
elsif resolved?
promise.resolve(@delayed ? @delayed[0] : value)
elsif rejected?
if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error)
promise.reject(@delayed ? @delayed[0] : error)
elsif promise.action.include?(:always)
promise.reject(@delayed ? @delayed[0] : error)
end
end
self
end
def resolve(value = nil)
if realized?
raise ArgumentError, 'the promise has already been realized'
end
if Promise === value
return (value << @prev) ^ self
end
begin
if block = @action[:success] || @action[:always]
value = block.call(value)
end
resolve!(value)
rescue Exception => e
exception!(e)
end
self
end
def resolve!(value)
@realized = :resolve
@value = value
if @next.any?
@next.each { |p| p.resolve(value) }
else
@delayed = [value]
end
end
def reject(value = nil)
if realized?
raise ArgumentError, 'the promise has already been realized'
end
if Promise === value
return (value << @prev) ^ self
end
begin
if block = @action[:failure] || @action[:always]
value = block.call(value)
end
if @action.has_key?(:always)
resolve!(value)
else
reject!(value)
end
rescue Exception => e
exception!(e)
end
self
end
def reject!(value)
@realized = :reject
@error = value
if @next.any?
@next.each { |p| p.reject(value) }
else
@delayed = [value]
end
end
def exception!(error)
@exception = true
reject!(error)
end
def then(&block)
self ^ Promise.new(success: block)
end
def then!(&block)
there_can_be_only_one!
self.then(&block)
end
alias do then
alias do! then!
def fail(&block)
self ^ Promise.new(failure: block)
end
def fail!(&block)
there_can_be_only_one!
fail(&block)
end
alias rescue fail
alias catch fail
alias rescue! fail!
alias catch! fail!
def always(&block)
self ^ Promise.new(always: block)
end
def always!(&block)
there_can_be_only_one!
always(&block)
end
alias finally always
alias ensure always
alias finally! always!
alias ensure! always!
def trace(depth = nil, &block)
self ^ Trace.new(depth, block)
end
def trace!(*args, &block)
there_can_be_only_one!
trace(*args, &block)
end
def there_can_be_only_one!
if @next.any?
raise ArgumentError, 'a promise has already been chained'
end
end
def inspect
result = "#<#{self.class}(#{object_id})"
if @next.any?
result += " >> #{@next.inspect}"
end
if realized?
result += ": #{(@value || @error).inspect}>"
else
result += ">"
end
result
end
class Trace < self
def self.it(promise)
current = []
if promise.act? || promise.prev.nil?
current.push(promise.value)
end
if prev = promise.prev
current.concat(it(prev))
else
current
end
end
def initialize(depth, block)
@depth = depth
super success: proc {
trace = Trace.it(self).reverse
trace.pop
if depth && depth <= trace.length
trace.shift(trace.length - depth)
end
block.call(*trace)
}
end
end
class When < self
def initialize(promises = [])
super()
@wait = []
promises.each {|promise|
wait promise
}
end
def each(&block)
raise ArgumentError, 'no block given' unless block
self.then {|values|
values.each(&block)
}
end
def collect(&block)
raise ArgumentError, 'no block given' unless block
self.then {|values|
When.new(values.map(&block))
}
end
def inject(*args, &block)
self.then {|values|
values.reduce(*args, &block)
}
end
alias map collect
alias reduce inject
def wait(promise)
unless Promise === promise
promise = Promise.value(promise)
end
if promise.act?
promise = promise.then
end
@wait << promise
promise.always {
try if @next.any?
}
self
end
alias and wait
def >>(*)
super.tap {
try
}
end
def try
if @wait.all?(&:realized?)
if promise = @wait.find(&:rejected?)
reject(promise.error)
else
resolve(@wait.map(&:value))
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment