Created
January 17, 2017 16:42
-
-
Save catmando/6fae8e6fdb3d2d11f2e3f46bb5beb580 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 [email protected]_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