Permalink
Fetching contributors…
Cannot retrieve contributors at this time
137 lines (109 sloc) 3.04 KB
module GraphQL::Batch
class Loader
NoExecutorError = GraphQL::Batch::NoExecutorError
deprecate_constant :NoExecutorError if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3")
def self.for(*group_args)
loader_key = loader_key_for(*group_args)
executor = Executor.current
unless executor
raise GraphQL::Batch::NoExecutorError, 'Cannot create loader without'\
' an Executor. Wrap the call to `for` with `GraphQL::Batch.batch`'\
' or use `GraphQL::Batch::Setup` as a query instrumenter if'\
' using with `graphql-ruby`'
end
executor.loader(loader_key) { new(*group_args) }
end
def self.loader_key_for(*group_args)
[self].concat(group_args)
end
def self.load(key)
self.for.load(key)
end
def self.load_many(keys)
self.for.load_many(keys)
end
attr_accessor :loader_key, :executor
def load(key)
cache[cache_key(key)] ||= begin
queue << key
::Promise.new.tap { |promise| promise.source = self }
end
end
def load_many(keys)
::Promise.all(keys.map { |key| load(key) })
end
def resolve #:nodoc:
return if resolved?
load_keys = queue
@queue = nil
perform(load_keys)
check_for_broken_promises(load_keys)
rescue => err
reject_pending_promises(load_keys, err)
end
# For Promise#sync
def wait #:nodoc:
if executor
executor.resolve(self)
else
resolve
end
end
def resolved?
@queue.nil? || @queue.empty?
end
protected
# Fulfill the key with provided value, for use in #perform
def fulfill(key, value)
finish_resolve(key) do |promise|
promise.fulfill(value)
end
end
def reject(key, reason)
finish_resolve(key) do |promise|
promise.reject(reason)
end
end
# Returns true when the key has already been fulfilled, otherwise returns false
def fulfilled?(key)
promise_for(key).fulfilled?
end
# Must override to load the keys and call #fulfill for each key
def perform(keys)
raise NotImplementedError
end
# Override to use a different key for the cache than the load key
def cache_key(load_key)
load_key
end
private
def finish_resolve(key)
promise = promise_for(key)
return yield(promise) unless executor
executor.around_promise_callbacks do
yield promise
end
end
def cache
@cache ||= {}
end
def queue
@queue ||= []
end
def promise_for(load_key)
cache.fetch(cache_key(load_key))
end
def reject_pending_promises(load_keys, err)
load_keys.each do |key|
next unless promise_for(key).pending?
reject(key, err)
end
end
def check_for_broken_promises(load_keys)
load_keys.each do |key|
next unless promise_for(key).pending?
reject(key, ::Promise::BrokenError.new("#{self.class} didn't fulfill promise for key #{key.inspect}"))
end
end
end
end