Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.