Created
June 28, 2018 21:03
-
-
Save jgaskins/66d2d9b4f21288e0cd1f9d9483624e44 to your computer and use it in GitHub Desktop.
Rendering a promise with Clearwater
This file contains 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
require 'clearwater/black_box_node' | |
require 'clearwater/component' | |
require 'clearwater/application' | |
class RenderPromise | |
include Clearwater::BlackBoxNode | |
DEFAULT = proc { Clearwater::Component.span } | |
Wrapper = Struct.new(:render).include(Clearwater::Component) | |
attr_reader :app, :started_at, :wrapper, :promise | |
def initialize promise, &loaded | |
@promise = promise | |
@delay = {} | |
@loaded = loaded || DEFAULT | |
@begin = DEFAULT | |
@error = DEFAULT | |
@timer_ids = [] | |
end | |
def node | |
@begin.call | |
end | |
def begin &block | |
@begin = block if block_given? | |
self | |
end | |
def delay seconds, &block | |
@delay[seconds] = block if block_given? | |
self | |
end | |
def loaded &block | |
@loaded = block if block_given? | |
self | |
end | |
def error &block | |
@error = block if block_given? | |
self | |
end | |
def mount element | |
@started_at = Time.now | |
@active = true | |
@wrapper ||= Wrapper.new | |
@app ||= Clearwater::Application.new( | |
component: @wrapper, | |
element: element, | |
) | |
wait_for_resolution | |
end | |
def update previous, element | |
@app = previous.app | |
@wrapper = previous.wrapper | |
previous.deactivate! | |
if @promise.equal? previous.promise | |
@started_at = previous.started_at | |
else | |
mount element | |
return | |
end | |
if @promise.resolved? | |
@wrapper.render = @loaded.call @promise.value | |
@app.render | |
elsif @promise.rejected? | |
@wrapper.render = @error.call @promise.error | |
@app.render | |
else | |
wait_for_resolution | |
end | |
rescue => e | |
@wrapper.render = @error.call e | |
@app.render | |
end | |
def unmount element | |
deactivate! | |
end | |
def wait_for_resolution | |
age = Time.now - started_at | |
render = @delay | |
.sort_by { |seconds, _| seconds } | |
.take_while { |seconds, _| age >= seconds.first } | |
wrapper.render = (render.last || [0, @begin]).last.call | |
# Stupid solution for exponential renders, but it works. | |
@current_render = rand | |
current_render = @current_render | |
@delay | |
.map { |seconds, _| seconds - age } | |
.select { |seconds| seconds > 0 } | |
.each do |seconds| | |
set_timer seconds do | |
if active? && current_render == @current_render | |
wait_for_resolution | |
end | |
end | |
end | |
@promise.then do |value| | |
if active? | |
@wrapper.render = begin | |
@loaded.call value | |
rescue => e | |
@error.call e | |
end | |
@app.render | |
end | |
end | |
@promise.catch do |error| | |
if active? | |
warn error | |
@wrapper.render = @error.call error | |
@app.render | |
end | |
end | |
@app.render | |
end | |
def active? | |
@active | |
end | |
def deactivate! | |
@active = false | |
@timer_ids.each do |id| | |
`clearTimeout(id)` | |
end | |
end | |
def set_timer delay, &block | |
@timer_ids << `setTimeout(function() { #{block.call} }, delay * 1000)` | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment