Skip to content

Commit 902f3ee

Browse files
authored
Allow loaders to be used without an executor (#35)
Promise#source is used to sync the loaders promises
1 parent f9f00cd commit 902f3ee

File tree

6 files changed

+50
-45
lines changed

6 files changed

+50
-45
lines changed

lib/graphql/batch/execution_strategy.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
module GraphQL::Batch
22
class ExecutionStrategy < GraphQL::Query::SerialExecution
33
def execute(_, _, query)
4-
Promise.sync(as_promise_unless_resolved(super))
4+
deep_sync(super)
55
rescue GraphQL::InvalidNullError => err
66
err.parent_error? || query.context.errors.push(err)
77
nil
88
ensure
99
GraphQL::Batch::Executor.current.clear
1010
end
1111

12+
def deep_sync(result)
13+
Promise.sync(as_promise_unless_resolved(result))
14+
end
15+
1216
private
1317

1418
def as_promise_unless_resolved(result)
@@ -21,7 +25,7 @@ def as_promise_unless_resolved(result)
2125
end
2226
end
2327
return result if all_promises.empty?
24-
Promise.all(all_promises).then { result }
28+
::Promise.all(all_promises).then { result }
2529
end
2630

2731
def each_promise(obj, &block)

lib/graphql/batch/executor.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,16 @@ def initialize
1919
@loading = false
2020
end
2121

22+
def resolve(loader)
23+
with_loading(true) { loader.resolve }
24+
end
25+
2226
def shift
2327
@loaders.shift.last
2428
end
2529

2630
def tick
27-
with_loading(true) { shift.resolve }
28-
end
29-
30-
def wait(promise)
31-
tick while promise.pending? && !loaders.empty?
32-
if promise.pending?
33-
promise.reject(::Promise::BrokenError.new("Promise wasn't fulfilled after all queries were loaded"))
34-
end
31+
resolve(shift)
3532
end
3633

3734
def wait_all

lib/graphql/batch/loader.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ module GraphQL::Batch
22
class Loader
33
def self.for(*group_args)
44
loader_key = [self].concat(group_args)
5-
Executor.current.loaders[loader_key] ||= new(*group_args).tap do |loader|
5+
executor = Executor.current
6+
executor.loaders[loader_key] ||= new(*group_args).tap do |loader|
67
loader.loader_key = loader_key
8+
loader.executor = executor
79
end
810
end
911

@@ -15,21 +17,17 @@ def self.load_many(keys)
1517
self.for.load_many(keys)
1618
end
1719

18-
attr_accessor :loader_key
20+
attr_accessor :loader_key, :executor
1921

2022
def load(key)
21-
loader = Executor.current.loaders[loader_key] ||= self
22-
if loader != self
23-
raise "load called on loader that wasn't registered with executor"
24-
end
2523
cache[cache_key(key)] ||= begin
2624
queue << key
27-
Promise.new
25+
Promise.new.tap { |promise| promise.source = self }
2826
end
2927
end
3028

3129
def load_many(keys)
32-
Promise.all(keys.map { |key| load(key) })
30+
::Promise.all(keys.map { |key| load(key) })
3331
end
3432

3533
def resolve #:nodoc:
@@ -44,6 +42,16 @@ def resolve #:nodoc:
4442
end
4543
end
4644

45+
# For Promise#sync
46+
def wait #:nodoc:
47+
if executor
48+
executor.loaders.delete(loader_key)
49+
executor.resolve(self)
50+
else
51+
resolve
52+
end
53+
end
54+
4755
protected
4856

4957
# Fulfill the key with provided value, for use in #perform

lib/graphql/batch/mutation_execution_strategy.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@ class MutationExecutionStrategy < GraphQL::Batch::ExecutionStrategy
44

55
class FieldResolution < GraphQL::Batch::ExecutionStrategy::FieldResolution
66
def get_finished_value(raw_value)
7-
return super if execution_context.strategy.enable_batching
7+
strategy = execution_context.strategy
8+
return super if strategy.enable_batching
89

9-
raw_value = Promise.sync(raw_value)
10-
11-
execution_context.strategy.enable_batching = true
1210
begin
13-
result = super(raw_value)
14-
GraphQL::Batch::Executor.current.wait_all
15-
result
11+
strategy.enable_batching = true
12+
strategy.deep_sync(Promise.sync(super))
1613
ensure
17-
execution_context.strategy.enable_batching = false
14+
strategy.enable_batching = false
1815
end
1916
end
2017
end

lib/graphql/batch/promise.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
module GraphQL::Batch
22
class Promise < ::Promise
3-
def wait
4-
Executor.current.wait(self)
5-
end
6-
73
def defer
8-
Executor.current.defer { super }
4+
executor = Executor.current
5+
executor ? executor.defer { super } : super
96
end
107
end
118
end

test/loader_test.rb

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ def test_then
7979

8080
def test_then_error
8181
query = GroupCountLoader.for("single").load(:a).then { raise "oops" }
82-
assert_raises(RuntimeError, 'oops') do
82+
err = assert_raises(RuntimeError) do
8383
query.sync
8484
end
85+
assert_equal 'oops', err.message
8586
end
8687

8788
def test_on_reject_without_error
@@ -97,13 +98,6 @@ def test_query_in_callback
9798
assert_equal 5, EchoLoader.load(4).then { |value| EchoLoader.load(value + 1) }.sync
9899
end
99100

100-
def test_broken_promise_executor_check
101-
promise = GraphQL::Batch::Promise.new
102-
promise.wait
103-
assert_equal GraphQL::Batch::BrokenPromiseError, promise.reason.class
104-
assert_equal "Promise wasn't fulfilled after all queries were loaded", promise.reason.message
105-
end
106-
107101
def test_broken_promise_loader_check
108102
promise = BrokenLoader.load(1)
109103
promise.wait
@@ -131,15 +125,12 @@ def test_load_on_different_loaders
131125
loader = EchoLoader.for
132126
assert_equal :a, loader.load(:a).sync
133127
loader2 = EchoLoader.for
134-
promise = loader2.load(:b)
135128

136-
err = assert_raises(RuntimeError) do
137-
loader.load(:c)
138-
end
129+
promise = loader2.load(:b)
130+
promise2 = loader.load(:c)
139131

140-
assert_equal "load called on loader that wasn't registered with executor", err.message
141132
assert_equal :b, promise.sync
142-
assert_equal :c, loader.load(:c).sync
133+
assert_equal :c, promise2.sync
143134
end
144135

145136
def test_derived_cache_key
@@ -150,4 +141,15 @@ def test_loader_for_without_load
150141
loader = EchoLoader.for
151142
GraphQL::Batch::Executor.current.wait_all
152143
end
144+
145+
def test_loader_without_executor
146+
loader1 = GroupCountLoader.new('one')
147+
loader2 = GroupCountLoader.new('two')
148+
group = Promise.all([
149+
loader2.load(:a),
150+
loader1.load(:a),
151+
loader2.load(:b),
152+
])
153+
assert_equal [2, 1, 2], group.sync
154+
end
153155
end

0 commit comments

Comments
 (0)