Skip to content

Instantly share code, notes, and snippets.

@jgaskins
Created June 28, 2018 21:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jgaskins/66d2d9b4f21288e0cd1f9d9483624e44 to your computer and use it in GitHub Desktop.
Save jgaskins/66d2d9b4f21288e0cd1f9d9483624e44 to your computer and use it in GitHub Desktop.
Rendering a promise with Clearwater
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