promise.rb 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +6 -0
- data/Rakefile +7 -1
- data/benchmark/run.rb +75 -0
- data/benchmark/setup.rb +22 -0
- data/config/flog.yml +1 -1
- data/config/reek.yml +5 -9
- data/config/rubocop.yml +1 -1
- data/lib/promise.rb +92 -30
- data/lib/promise/group.rb +23 -11
- data/lib/promise/observer.rb +11 -0
- data/lib/promise/version.rb +1 -1
- data/promise.rb.gemspec +3 -0
- data/spec/unit/promise_spec.rb +413 -14
- metadata +47 -3
- data/lib/promise/callback.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18cdca70f20edeb0b04ea3ca124ac37b9421e7e1
|
4
|
+
data.tar.gz: 34498e438956c5f0c6a9e689c8cd9b2ad56c5231
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7dc20a5fb4be8e4fc352c243f03528a9458cce898051fcb3c2737f7b5a767de2049bdbb522122c6f938af17beaca055b715ca1edb94db968fc6e27f65eb15d28
|
7
|
+
data.tar.gz: 1c97f288a31cff1aa11ef0024026a7fca37818506a0ea804b7fb1fa80b546208e7bcb7b5e74fe4a37fa8a011992b14017ce13c6c0cae9ac7299fce076f540f42
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
if Gem.ruby_version >= Gem::Version.new('2.
|
3
|
+
if Gem.ruby_version >= Gem::Version.new('2.2')
|
4
4
|
require 'devtools'
|
5
5
|
Devtools.init_rake_tasks
|
6
6
|
|
@@ -19,3 +19,9 @@ else
|
|
19
19
|
RSpec::Core::RakeTask.new(:spec)
|
20
20
|
task :default => :spec
|
21
21
|
end
|
22
|
+
|
23
|
+
|
24
|
+
desc "Run the benchmark suite in benchmark/run.rb"
|
25
|
+
task :benchmark do
|
26
|
+
require "./benchmark/run.rb"
|
27
|
+
end
|
data/benchmark/run.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './setup.rb'
|
4
|
+
|
5
|
+
PromiseBenchmark.profile_memory do
|
6
|
+
Promise.all([
|
7
|
+
Promise.all([
|
8
|
+
Promise.resolve(1),
|
9
|
+
Promise.resolve(2),
|
10
|
+
Promise.resolve(3)
|
11
|
+
]),
|
12
|
+
Promise.all([
|
13
|
+
Promise.resolve(1),
|
14
|
+
Promise.resolve(2),
|
15
|
+
Promise.resolve(3)
|
16
|
+
]),
|
17
|
+
Promise.all([
|
18
|
+
Promise.resolve(1),
|
19
|
+
Promise.resolve(2),
|
20
|
+
Promise.resolve(3)
|
21
|
+
])
|
22
|
+
]).then { |value| value }.sync
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "\n"
|
26
|
+
|
27
|
+
PromiseBenchmark.benchmark do |x|
|
28
|
+
x.report 'Promise.all w/promises' do
|
29
|
+
Promise.all([
|
30
|
+
Promise.all([
|
31
|
+
Promise.resolve(1),
|
32
|
+
Promise.resolve(2),
|
33
|
+
Promise.resolve(3)
|
34
|
+
]),
|
35
|
+
Promise.all([
|
36
|
+
Promise.resolve(1),
|
37
|
+
Promise.resolve(2),
|
38
|
+
Promise.resolve(3)
|
39
|
+
]),
|
40
|
+
Promise.all([
|
41
|
+
Promise.resolve(1),
|
42
|
+
Promise.resolve(2),
|
43
|
+
Promise.resolve(3)
|
44
|
+
])
|
45
|
+
]).then { |value| value }
|
46
|
+
end
|
47
|
+
x.report 'Promise.all w/values' do
|
48
|
+
Promise.all([
|
49
|
+
Promise.all([
|
50
|
+
1,
|
51
|
+
2,
|
52
|
+
3
|
53
|
+
]),
|
54
|
+
Promise.all([
|
55
|
+
1,
|
56
|
+
2,
|
57
|
+
3
|
58
|
+
]),
|
59
|
+
Promise.all([
|
60
|
+
1,
|
61
|
+
2,
|
62
|
+
3
|
63
|
+
])
|
64
|
+
]).then { |value| value }.sync
|
65
|
+
end
|
66
|
+
x.report('Promise.resolve') { Promise.resolve(true) }
|
67
|
+
x.report('Promise.resolve.sync') { Promise.resolve(true).sync }
|
68
|
+
x.report('Promise.resolve#then') do
|
69
|
+
Promise.resolve(true).then { |value| value }.sync
|
70
|
+
end
|
71
|
+
x.report('Promise.new#then') { Promise.new.then { |value| value } }
|
72
|
+
x.report('Promise.resolve nested') do
|
73
|
+
Promise.resolve(true).then { |_value| Promise.resolve(false) }.sync
|
74
|
+
end
|
75
|
+
end
|
data/benchmark/setup.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'promise'
|
4
|
+
require 'benchmark/ips'
|
5
|
+
require 'benchmark/memory'
|
6
|
+
require 'memory_profiler'
|
7
|
+
|
8
|
+
module PromiseBenchmark
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Pass a block which will be benchmarked
|
12
|
+
def benchmark
|
13
|
+
Benchmark.ips { |x| yield(x) }
|
14
|
+
Benchmark.memory { |x| yield(x) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Pass a block which will be profiled for memory usage
|
18
|
+
def profile_memory
|
19
|
+
report = MemoryProfiler.report { yield }
|
20
|
+
report.pretty_print
|
21
|
+
end
|
22
|
+
end
|
data/config/flog.yml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
---
|
2
|
-
threshold:
|
2
|
+
threshold: 15.8
|
data/config/reek.yml
CHANGED
@@ -33,7 +33,7 @@ IrresponsibleModule:
|
|
33
33
|
LongParameterList:
|
34
34
|
enabled: true
|
35
35
|
exclude: []
|
36
|
-
max_params:
|
36
|
+
max_params: 3
|
37
37
|
overrides:
|
38
38
|
initialize:
|
39
39
|
max_params: 4
|
@@ -50,9 +50,7 @@ NilCheck:
|
|
50
50
|
enabled: true
|
51
51
|
exclude: []
|
52
52
|
RepeatedConditional:
|
53
|
-
enabled:
|
54
|
-
exclude: []
|
55
|
-
max_ifs: 4
|
53
|
+
enabled: false
|
56
54
|
TooManyInstanceVariables:
|
57
55
|
enabled: true
|
58
56
|
exclude: []
|
@@ -60,11 +58,7 @@ TooManyInstanceVariables:
|
|
60
58
|
TooManyMethods:
|
61
59
|
enabled: false
|
62
60
|
TooManyStatements:
|
63
|
-
enabled:
|
64
|
-
exclude:
|
65
|
-
- initialize
|
66
|
-
- each
|
67
|
-
max_statements: 7
|
61
|
+
enabled: false
|
68
62
|
UncommunicativeMethodName:
|
69
63
|
enabled: true
|
70
64
|
exclude: []
|
@@ -103,3 +97,5 @@ UtilityFunction:
|
|
103
97
|
enabled: false
|
104
98
|
exclude: []
|
105
99
|
max_helper_calls: 0
|
100
|
+
InstanceVariableAssumption:
|
101
|
+
enabled: false
|
data/config/rubocop.yml
CHANGED
data/lib/promise.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'promise/version'
|
4
4
|
|
5
|
-
require 'promise/
|
5
|
+
require 'promise/observer'
|
6
6
|
require 'promise/progress'
|
7
7
|
require 'promise/group'
|
8
8
|
|
@@ -11,6 +11,7 @@ class Promise
|
|
11
11
|
BrokenError = Class.new(Error)
|
12
12
|
|
13
13
|
include Promise::Progress
|
14
|
+
include Promise::Observer
|
14
15
|
|
15
16
|
attr_accessor :source
|
16
17
|
attr_reader :state, :value, :reason
|
@@ -38,7 +39,6 @@ class Promise
|
|
38
39
|
|
39
40
|
def initialize
|
40
41
|
@state = :pending
|
41
|
-
@callbacks = []
|
42
42
|
end
|
43
43
|
|
44
44
|
def pending?
|
@@ -57,7 +57,16 @@ class Promise
|
|
57
57
|
on_fulfill ||= block
|
58
58
|
next_promise = self.class.new
|
59
59
|
|
60
|
-
|
60
|
+
case state
|
61
|
+
when :fulfilled
|
62
|
+
defer { next_promise.promise_fulfilled(value, on_fulfill) }
|
63
|
+
when :rejected
|
64
|
+
defer { next_promise.promise_rejected(reason, on_reject) }
|
65
|
+
else
|
66
|
+
next_promise.source = self
|
67
|
+
subscribe(next_promise, on_fulfill, on_reject)
|
68
|
+
end
|
69
|
+
|
61
70
|
next_promise
|
62
71
|
end
|
63
72
|
|
@@ -76,24 +85,39 @@ class Promise
|
|
76
85
|
end
|
77
86
|
|
78
87
|
def fulfill(value = nil)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
return self unless pending?
|
89
|
+
|
90
|
+
if value.is_a?(Promise)
|
91
|
+
case value.state
|
92
|
+
when :fulfilled
|
93
|
+
fulfill(value.value)
|
94
|
+
when :rejected
|
95
|
+
reject(value.reason)
|
96
|
+
else
|
97
|
+
@source = value
|
98
|
+
value.subscribe(self, nil, nil)
|
86
99
|
end
|
100
|
+
else
|
101
|
+
@source = nil
|
102
|
+
|
103
|
+
@state = :fulfilled
|
104
|
+
@value = value
|
105
|
+
|
106
|
+
notify_fulfillment if defined?(@observers)
|
87
107
|
end
|
108
|
+
|
88
109
|
self
|
89
110
|
end
|
90
111
|
|
91
112
|
def reject(reason = nil)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
113
|
+
return self unless pending?
|
114
|
+
|
115
|
+
@source = nil
|
116
|
+
@state = :rejected
|
117
|
+
@reason = reason_coercion(reason || Error)
|
118
|
+
|
119
|
+
notify_rejection if defined?(@observers)
|
120
|
+
|
97
121
|
self
|
98
122
|
end
|
99
123
|
|
@@ -107,6 +131,29 @@ class Promise
|
|
107
131
|
end
|
108
132
|
end
|
109
133
|
|
134
|
+
# Subscribe the given `observer` for status changes of a `Promise`.
|
135
|
+
#
|
136
|
+
# The observer will be notified about state changes of the promise
|
137
|
+
# by calls to its `#promise_fulfilled` or `#promise_rejected` methods.
|
138
|
+
#
|
139
|
+
# These methods will be called with two arguments,
|
140
|
+
# the first being the observed `Promise`, the second being the
|
141
|
+
# `on_fulfill_arg` or `on_reject_arg` given to `#subscribe`.
|
142
|
+
#
|
143
|
+
# @param [Promise::Observer] observer
|
144
|
+
# @param [Object] on_fulfill_arg
|
145
|
+
# @param [Object] on_reject_arg
|
146
|
+
def subscribe(observer, on_fulfill_arg, on_reject_arg)
|
147
|
+
raise Error, 'Non-pending promises can not be observed' unless pending?
|
148
|
+
|
149
|
+
unless observer.is_a?(Observer)
|
150
|
+
raise ArgumentError, 'Expected `observer` to be a `Promise::Observer`'
|
151
|
+
end
|
152
|
+
|
153
|
+
@observers ||= []
|
154
|
+
@observers.push(observer, on_fulfill_arg, on_reject_arg)
|
155
|
+
end
|
156
|
+
|
110
157
|
protected
|
111
158
|
|
112
159
|
# Override to defer calling the callback for Promises/A+ spec compliance
|
@@ -114,12 +161,19 @@ class Promise
|
|
114
161
|
yield
|
115
162
|
end
|
116
163
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
120
|
-
|
164
|
+
def promise_fulfilled(value, on_fulfill)
|
165
|
+
if on_fulfill
|
166
|
+
settle_from_handler(value, &on_fulfill)
|
167
|
+
else
|
168
|
+
fulfill(value)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def promise_rejected(reason, on_reject)
|
173
|
+
if on_reject
|
174
|
+
settle_from_handler(reason, &on_reject)
|
121
175
|
else
|
122
|
-
|
176
|
+
reject(reason)
|
123
177
|
end
|
124
178
|
end
|
125
179
|
|
@@ -135,21 +189,29 @@ class Promise
|
|
135
189
|
reason
|
136
190
|
end
|
137
191
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
192
|
+
def notify_fulfillment
|
193
|
+
defer do
|
194
|
+
@observers.each_slice(3) do |observer, on_fulfill_arg|
|
195
|
+
observer.promise_fulfilled(value, on_fulfill_arg)
|
196
|
+
end
|
197
|
+
|
198
|
+
@observers = nil
|
143
199
|
end
|
144
200
|
end
|
145
201
|
|
146
|
-
def
|
202
|
+
def notify_rejection
|
147
203
|
defer do
|
148
|
-
|
149
|
-
|
150
|
-
else
|
151
|
-
callback.reject(reason)
|
204
|
+
@observers.each_slice(3) do |observer, _on_fulfill_arg, on_reject_arg|
|
205
|
+
observer.promise_rejected(reason, on_reject_arg)
|
152
206
|
end
|
207
|
+
|
208
|
+
@observers = nil
|
153
209
|
end
|
154
210
|
end
|
211
|
+
|
212
|
+
def settle_from_handler(value)
|
213
|
+
fulfill(yield(value))
|
214
|
+
rescue => ex
|
215
|
+
reject(ex)
|
216
|
+
end
|
155
217
|
end
|
data/lib/promise/group.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
class Promise
|
2
2
|
class Group
|
3
|
+
include Promise::Observer
|
4
|
+
|
3
5
|
attr_accessor :source
|
4
6
|
attr_reader :promise
|
5
7
|
|
@@ -7,6 +9,7 @@ class Promise
|
|
7
9
|
@promise = result_promise
|
8
10
|
@inputs = inputs
|
9
11
|
@remaining = count_promises
|
12
|
+
|
10
13
|
if @remaining.zero?
|
11
14
|
promise.fulfill(inputs)
|
12
15
|
else
|
@@ -21,17 +24,7 @@ class Promise
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
def chain_inputs
|
27
|
-
on_fulfill = method(:on_fulfill)
|
28
|
-
on_reject = promise.public_method(:reject)
|
29
|
-
each_promise do |input_promise|
|
30
|
-
input_promise.then(on_fulfill, on_reject)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def on_fulfill(_result)
|
27
|
+
def promise_fulfilled(_value = nil, _arg = nil)
|
35
28
|
@remaining -= 1
|
36
29
|
if @remaining.zero?
|
37
30
|
result = @inputs.map { |obj| promise?(obj) ? obj.value : obj }
|
@@ -39,6 +32,25 @@ class Promise
|
|
39
32
|
end
|
40
33
|
end
|
41
34
|
|
35
|
+
def promise_rejected(reason, _arg = nil)
|
36
|
+
promise.reject(reason)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def chain_inputs
|
42
|
+
each_promise do |input_promise|
|
43
|
+
case input_promise.state
|
44
|
+
when :fulfilled
|
45
|
+
promise_fulfilled
|
46
|
+
when :rejected
|
47
|
+
promise_rejected(input_promise.reason)
|
48
|
+
else
|
49
|
+
input_promise.subscribe(self, nil, nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
42
54
|
def promise?(obj)
|
43
55
|
obj.is_a?(Promise)
|
44
56
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Promise
|
2
|
+
# The `Promise::Observer` module allows an object to be
|
3
|
+
# notified of `Promise` state changes.
|
4
|
+
#
|
5
|
+
# See `Promise#subscribe`.
|
6
|
+
module Observer
|
7
|
+
def promise_fulfilled(_value, _on_fulfill_arg); end
|
8
|
+
|
9
|
+
def promise_rejected(_reason, _on_reject_arg); end
|
10
|
+
end
|
11
|
+
end
|
data/lib/promise/version.rb
CHANGED
data/promise.rb.gemspec
CHANGED
@@ -19,4 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_development_dependency 'rspec'
|
22
|
+
spec.add_development_dependency 'benchmark-ips'
|
23
|
+
spec.add_development_dependency 'benchmark-memory'
|
24
|
+
spec.add_development_dependency 'memory_profiler'
|
22
25
|
end
|
data/spec/unit/promise_spec.rb
CHANGED
@@ -42,10 +42,6 @@ describe Promise do
|
|
42
42
|
subject.fulfill(other_value)
|
43
43
|
expect(subject.value).to eq(value)
|
44
44
|
end
|
45
|
-
|
46
|
-
it 'freezes the value' do
|
47
|
-
skip 'Dropped in 74da6e9'
|
48
|
-
end
|
49
45
|
end
|
50
46
|
|
51
47
|
describe '3.1.3 rejected' do
|
@@ -62,10 +58,6 @@ describe Promise do
|
|
62
58
|
subject.reject(other_reason)
|
63
59
|
expect(subject.reason).to eq(reason)
|
64
60
|
end
|
65
|
-
|
66
|
-
it 'freezes the reason' do
|
67
|
-
skip 'Dropped in 74da6e9'
|
68
|
-
end
|
69
61
|
end
|
70
62
|
|
71
63
|
describe '3.2.1 on_fulfill' do
|
@@ -169,7 +161,7 @@ describe Promise do
|
|
169
161
|
end
|
170
162
|
|
171
163
|
describe '3.2.4' do
|
172
|
-
it 'returns before on_fulfill
|
164
|
+
it 'returns before on_fulfill is called when fulfilling a promise' do
|
173
165
|
called = false
|
174
166
|
p1 = DelayedPromise.new
|
175
167
|
p2 = p1.then { called = true }
|
@@ -181,6 +173,57 @@ describe Promise do
|
|
181
173
|
expect(called).to eq(true)
|
182
174
|
expect(p2).to be_fulfilled
|
183
175
|
end
|
176
|
+
|
177
|
+
it 'returns before on_reject is called when rejecting a promise' do
|
178
|
+
called = false
|
179
|
+
p1 = DelayedPromise.new
|
180
|
+
p2 = p1.then(nil, lambda do |err|
|
181
|
+
called = true
|
182
|
+
raise err
|
183
|
+
end)
|
184
|
+
|
185
|
+
p1.reject(42)
|
186
|
+
|
187
|
+
expect(called).to eq(false)
|
188
|
+
DelayedPromise.call_deferred
|
189
|
+
expect(called).to eq(true)
|
190
|
+
expect(p2).to be_rejected
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'returns before on_fulfill is called for a fulfilled promise' do
|
194
|
+
called = false
|
195
|
+
p1 = DelayedPromise.new
|
196
|
+
p1.fulfill(42)
|
197
|
+
|
198
|
+
p2 = p1.then { called = true }
|
199
|
+
|
200
|
+
expect(p2).to be_pending
|
201
|
+
expect(called).to eq(false)
|
202
|
+
|
203
|
+
DelayedPromise.call_deferred
|
204
|
+
|
205
|
+
expect(called).to eq(true)
|
206
|
+
expect(p2).to be_fulfilled
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'returns before on_reject is called for a rejected promise' do
|
210
|
+
called = false
|
211
|
+
p1 = DelayedPromise.new
|
212
|
+
p1.reject(42)
|
213
|
+
|
214
|
+
p2 = p1.then(nil, lambda { |err|
|
215
|
+
called = true
|
216
|
+
raise err
|
217
|
+
})
|
218
|
+
|
219
|
+
expect(p2).to be_pending
|
220
|
+
expect(called).to eq(false)
|
221
|
+
|
222
|
+
DelayedPromise.call_deferred
|
223
|
+
|
224
|
+
expect(called).to eq(true)
|
225
|
+
expect(p2).to be_rejected
|
226
|
+
end
|
184
227
|
end
|
185
228
|
|
186
229
|
describe '3.2.5' do
|
@@ -346,6 +389,343 @@ describe Promise do
|
|
346
389
|
end
|
347
390
|
end
|
348
391
|
|
392
|
+
describe '#fulfill' do
|
393
|
+
describe 'when called on a pending Promise' do
|
394
|
+
it 'fulfills the Promise with the given value' do
|
395
|
+
promise = Promise.new
|
396
|
+
promise.fulfill(:foo)
|
397
|
+
|
398
|
+
expect(promise).to be_fulfilled
|
399
|
+
expect(promise.value).to equal(:foo)
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'can be called without arguments' do
|
403
|
+
promise = Promise.new
|
404
|
+
promise.fulfill
|
405
|
+
|
406
|
+
expect(promise).to be_fulfilled
|
407
|
+
expect(promise.value).to be_nil
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'returns self on a pending promise' do
|
411
|
+
promise = Promise.new
|
412
|
+
expect(promise.fulfill(:foo)).to equal(promise)
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'returns self on a rejected promise' do
|
416
|
+
promise = Promise.new
|
417
|
+
promise.reject(:baz)
|
418
|
+
expect(promise.fulfill(:foo)).to equal(promise)
|
419
|
+
end
|
420
|
+
|
421
|
+
it 'unsets any `source` associations' do
|
422
|
+
other = Promise.new
|
423
|
+
|
424
|
+
promise = Promise.new
|
425
|
+
promise.fulfill(other)
|
426
|
+
|
427
|
+
other.fulfill(:foo)
|
428
|
+
|
429
|
+
expect(promise.source).to be_nil
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'unsets any references to previously set observers' do
|
433
|
+
promise = Promise.new
|
434
|
+
|
435
|
+
observer = Class.new { include Promise::Observer }.new
|
436
|
+
promise.subscribe(observer, nil, nil)
|
437
|
+
|
438
|
+
promise.fulfill(:foo)
|
439
|
+
|
440
|
+
expect(promise.instance_variable_get(:@observers)).to be_nil
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe 'when called on a fulfilled Promise' do
|
445
|
+
it 'returns self' do
|
446
|
+
promise = Promise.new
|
447
|
+
promise.fulfill(:foo)
|
448
|
+
|
449
|
+
expect(promise.fulfill(:bar)).to equal(promise)
|
450
|
+
end
|
451
|
+
|
452
|
+
it 'can not change the fulfillment value' do
|
453
|
+
promise = Promise.new
|
454
|
+
promise.fulfill(:foo)
|
455
|
+
promise.fulfill(:bar)
|
456
|
+
|
457
|
+
expect(promise).to be_fulfilled
|
458
|
+
expect(promise.value).to equal(:foo)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
describe 'when called on a rejected Promise' do
|
463
|
+
it 'returns self' do
|
464
|
+
promise = Promise.new
|
465
|
+
promise.reject(:foo)
|
466
|
+
|
467
|
+
expect(promise.fulfill(:bar)).to equal(promise)
|
468
|
+
end
|
469
|
+
|
470
|
+
it 'can not change the rejection reason' do
|
471
|
+
promise = Promise.new
|
472
|
+
promise.reject(:foo)
|
473
|
+
promise.fulfill(:bar)
|
474
|
+
|
475
|
+
expect(promise).to be_rejected
|
476
|
+
expect(promise.reason).to equal(:foo)
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'does not set a fulfilment value' do
|
480
|
+
promise = Promise.new
|
481
|
+
promise.reject(:foo)
|
482
|
+
promise.fulfill(:bar)
|
483
|
+
|
484
|
+
expect(promise.value).to be_nil
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
describe 'when the fulfillment value is a pending Promise' do
|
489
|
+
it 'leaves the promise in a pending state' do
|
490
|
+
other = Promise.new
|
491
|
+
|
492
|
+
promise = Promise.new
|
493
|
+
promise.fulfill(other)
|
494
|
+
|
495
|
+
expect(promise).to be_pending
|
496
|
+
end
|
497
|
+
|
498
|
+
it 'sets the given Promise as the source' do
|
499
|
+
other = Promise.new
|
500
|
+
|
501
|
+
promise = Promise.new
|
502
|
+
promise.fulfill(other)
|
503
|
+
|
504
|
+
expect(promise.source).to equal(other)
|
505
|
+
end
|
506
|
+
|
507
|
+
it 'propagates promise fulfillment' do
|
508
|
+
other = Promise.new
|
509
|
+
|
510
|
+
promise = Promise.new
|
511
|
+
promise.fulfill(other)
|
512
|
+
|
513
|
+
other.fulfill(:foo)
|
514
|
+
|
515
|
+
expect(promise).to be_fulfilled
|
516
|
+
expect(promise.value).to equal(:foo)
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'propagates promise rejection' do
|
520
|
+
other = Promise.new
|
521
|
+
|
522
|
+
promise = Promise.new
|
523
|
+
promise.fulfill(other)
|
524
|
+
|
525
|
+
other.reject(:foo)
|
526
|
+
|
527
|
+
expect(promise).to be_rejected
|
528
|
+
expect(promise.reason).to equal(:foo)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
describe 'when the fulfillment value is a fulfilled Promise' do
|
533
|
+
it 'fulfills the Promise with the same value' do
|
534
|
+
other = Promise.new
|
535
|
+
other.fulfill(:foo)
|
536
|
+
|
537
|
+
promise = Promise.new
|
538
|
+
promise.fulfill(other)
|
539
|
+
|
540
|
+
expect(promise).to be_fulfilled
|
541
|
+
expect(promise.value).to equal(:foo)
|
542
|
+
end
|
543
|
+
|
544
|
+
it 'works with Promise subclasses' do
|
545
|
+
other = Class.new(Promise).new
|
546
|
+
other.fulfill(:foo)
|
547
|
+
|
548
|
+
promise = Promise.new
|
549
|
+
promise.fulfill(other)
|
550
|
+
|
551
|
+
expect(promise).to be_fulfilled
|
552
|
+
expect(promise.value).to equal(:foo)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
describe 'when the fulfillment value is a rejected Promise' do
|
557
|
+
it 'fulfills the Promise with the same value' do
|
558
|
+
other = Promise.new
|
559
|
+
other.reject(:foo)
|
560
|
+
|
561
|
+
promise = Promise.new
|
562
|
+
promise.fulfill(other)
|
563
|
+
|
564
|
+
expect(promise).to be_rejected
|
565
|
+
expect(promise.reason).to equal(:foo)
|
566
|
+
end
|
567
|
+
|
568
|
+
it 'works with Promise subclasses' do
|
569
|
+
other = Class.new(Promise).new
|
570
|
+
other.reject(:foo)
|
571
|
+
|
572
|
+
promise = Promise.new
|
573
|
+
promise.fulfill(other)
|
574
|
+
|
575
|
+
expect(promise).to be_rejected
|
576
|
+
expect(promise.reason).to equal(:foo)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
describe '#reject' do
|
582
|
+
describe 'when called on a pending Promise' do
|
583
|
+
it 'rejects the Promise with the given value' do
|
584
|
+
promise = Promise.new
|
585
|
+
promise.reject(:foo)
|
586
|
+
|
587
|
+
expect(promise).to be_rejected
|
588
|
+
expect(promise.reason).to equal(:foo)
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'can be called without arguments' do
|
592
|
+
promise = Promise.new
|
593
|
+
promise.reject
|
594
|
+
|
595
|
+
expect(promise).to be_rejected
|
596
|
+
expect(promise.reason).to be_an_instance_of(Promise::Error)
|
597
|
+
end
|
598
|
+
|
599
|
+
it 'returns self' do
|
600
|
+
promise = Promise.new
|
601
|
+
expect(promise.reject(:foo)).to equal(promise)
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'unsets any `source` associations' do
|
605
|
+
other = Promise.new
|
606
|
+
|
607
|
+
promise = Promise.new
|
608
|
+
promise.fulfill(other)
|
609
|
+
|
610
|
+
other.reject(:foo)
|
611
|
+
|
612
|
+
expect(promise.source).to be_nil
|
613
|
+
end
|
614
|
+
|
615
|
+
it 'unsets any references to previously set observers' do
|
616
|
+
promise = Promise.new
|
617
|
+
|
618
|
+
observer = Class.new { include Promise::Observer }.new
|
619
|
+
promise.subscribe(observer, nil, nil)
|
620
|
+
|
621
|
+
promise.reject(:foo)
|
622
|
+
|
623
|
+
expect(promise.instance_variable_get(:@observers)).to be_nil
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
describe 'when called on a fulfilled Promise' do
|
628
|
+
it 'returns self' do
|
629
|
+
promise = Promise.new
|
630
|
+
promise.fulfill(:foo)
|
631
|
+
|
632
|
+
expect(promise.reject(:bar)).to equal(promise)
|
633
|
+
end
|
634
|
+
|
635
|
+
it 'can not change the fulfillment value' do
|
636
|
+
promise = Promise.new
|
637
|
+
promise.fulfill(:foo)
|
638
|
+
promise.reject(:bar)
|
639
|
+
|
640
|
+
expect(promise).to be_fulfilled
|
641
|
+
expect(promise.value).to equal(:foo)
|
642
|
+
end
|
643
|
+
|
644
|
+
it 'does not set a rejection reason' do
|
645
|
+
promise = Promise.new
|
646
|
+
promise.fulfill(:foo)
|
647
|
+
promise.reject(:bar)
|
648
|
+
|
649
|
+
expect(promise.reason).to be_nil
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
describe 'when called on a rejected Promise' do
|
654
|
+
it 'returns self' do
|
655
|
+
promise = Promise.new
|
656
|
+
promise.reject(:foo)
|
657
|
+
|
658
|
+
expect(promise.reject(:bar)).to equal(promise)
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'can not change the rejection reason' do
|
662
|
+
promise = Promise.new
|
663
|
+
promise.reject(:foo)
|
664
|
+
promise.reject(:bar)
|
665
|
+
|
666
|
+
expect(promise).to be_rejected
|
667
|
+
expect(promise.reason).to equal(:foo)
|
668
|
+
end
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
describe '#subscribe' do
|
673
|
+
it 'sets up the observer to be notified of promise fulfillment' do
|
674
|
+
promise = Promise.new
|
675
|
+
|
676
|
+
observer = Class.new { include Promise::Observer }.new
|
677
|
+
promise.subscribe(observer, :fulfill_arg, :reject_arg)
|
678
|
+
|
679
|
+
expect(observer).to receive(:promise_fulfilled).with(:foo, :fulfill_arg)
|
680
|
+
promise.fulfill(:foo)
|
681
|
+
end
|
682
|
+
|
683
|
+
it 'sets up the observer to be notified of promise rejection' do
|
684
|
+
promise = Promise.new
|
685
|
+
|
686
|
+
observer = Class.new { include Promise::Observer }.new
|
687
|
+
promise.subscribe(observer, :fulfill_arg, :reject_arg)
|
688
|
+
|
689
|
+
expect(observer).to receive(:promise_rejected).with(:foo, :reject_arg)
|
690
|
+
promise.reject(:foo)
|
691
|
+
end
|
692
|
+
|
693
|
+
it 'fails when called on a fulfilled promise' do
|
694
|
+
promise = Promise.new
|
695
|
+
promise.fulfill(:foo)
|
696
|
+
|
697
|
+
observer = Class.new { include Promise::Observer }.new
|
698
|
+
|
699
|
+
expected_message = 'Non-pending promises can not be observed'
|
700
|
+
expect {
|
701
|
+
promise.subscribe(observer, :fulfill_arg, :reject_arg)
|
702
|
+
}.to raise_error(Promise::Error, expected_message)
|
703
|
+
end
|
704
|
+
|
705
|
+
it 'fails when called on a rejected promise' do
|
706
|
+
promise = Promise.new
|
707
|
+
promise.reject(:foo)
|
708
|
+
|
709
|
+
observer = Class.new { include Promise::Observer }.new
|
710
|
+
|
711
|
+
expected_message = 'Non-pending promises can not be observed'
|
712
|
+
expect {
|
713
|
+
promise.subscribe(observer, :fulfill_arg, :reject_arg)
|
714
|
+
}.to raise_error(Promise::Error, expected_message)
|
715
|
+
end
|
716
|
+
|
717
|
+
it 'fails when the given observer is not a `Promise::Observer`' do
|
718
|
+
promise = Promise.new
|
719
|
+
|
720
|
+
observer = Object.new
|
721
|
+
|
722
|
+
expected_message = 'Expected `observer` to be a `Promise::Observer`'
|
723
|
+
expect {
|
724
|
+
promise.subscribe(observer, :fulfill_arg, :reject_arg)
|
725
|
+
}.to raise_error(ArgumentError, expected_message)
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
349
729
|
describe 'extras' do
|
350
730
|
describe '#rescue' do
|
351
731
|
it 'provides an on_reject callback' do
|
@@ -563,11 +943,20 @@ describe Promise do
|
|
563
943
|
end
|
564
944
|
|
565
945
|
describe '.all' do
|
566
|
-
it
|
567
|
-
|
568
|
-
promise = Promise.all(
|
569
|
-
|
570
|
-
expect(promise
|
946
|
+
it "fulfills the result with inputs if they don't contain promises" do
|
947
|
+
input = [1, 'b']
|
948
|
+
promise = Promise.all(input)
|
949
|
+
|
950
|
+
expect(promise).to be_fulfilled
|
951
|
+
expect(promise.value).to eq([1, 'b'])
|
952
|
+
end
|
953
|
+
|
954
|
+
it 'fulfills the result when all args are already fulfilled' do
|
955
|
+
input = [1, Promise.resolve(2.0)]
|
956
|
+
promise = Promise.all(input)
|
957
|
+
|
958
|
+
expect(promise).to be_fulfilled
|
959
|
+
expect(promise.value).to eq([1, 2.0])
|
571
960
|
end
|
572
961
|
|
573
962
|
it 'fulfills the result when all args are fulfilled' do
|
@@ -597,6 +986,16 @@ describe Promise do
|
|
597
986
|
expect(result.value).to eq(['a', :b])
|
598
987
|
end
|
599
988
|
|
989
|
+
it 'rejects the result when any input promise is already rejected' do
|
990
|
+
p1 = Promise.new
|
991
|
+
p2 = Promise.new.reject(:foo)
|
992
|
+
|
993
|
+
result = Promise.all([p1, p2])
|
994
|
+
|
995
|
+
expect(result).to be_rejected
|
996
|
+
expect(result.reason).to eq(:foo)
|
997
|
+
end
|
998
|
+
|
600
999
|
it 'rejects the result when any args is rejected' do
|
601
1000
|
p1 = Promise.new
|
602
1001
|
p2 = Promise.new
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: promise.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lars Gierth
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -24,6 +24,48 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark-ips
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: benchmark-memory
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: memory_profiler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
27
69
|
description: Promises/A+ for Ruby
|
28
70
|
email:
|
29
71
|
- lars.gierth@gmail.com
|
@@ -39,6 +81,8 @@ files:
|
|
39
81
|
- README.md
|
40
82
|
- Rakefile
|
41
83
|
- UNLICENSE
|
84
|
+
- benchmark/run.rb
|
85
|
+
- benchmark/setup.rb
|
42
86
|
- config/devtools.yml
|
43
87
|
- config/flay.yml
|
44
88
|
- config/flog.yml
|
@@ -47,8 +91,8 @@ files:
|
|
47
91
|
- config/rubocop.yml
|
48
92
|
- config/yardstick.yml
|
49
93
|
- lib/promise.rb
|
50
|
-
- lib/promise/callback.rb
|
51
94
|
- lib/promise/group.rb
|
95
|
+
- lib/promise/observer.rb
|
52
96
|
- lib/promise/progress.rb
|
53
97
|
- lib/promise/version.rb
|
54
98
|
- promise.rb.gemspec
|
data/lib/promise/callback.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
class Promise
|
4
|
-
class Callback
|
5
|
-
attr_accessor :source
|
6
|
-
|
7
|
-
def initialize(on_fulfill, on_reject, next_promise)
|
8
|
-
@on_fulfill = on_fulfill
|
9
|
-
@on_reject = on_reject
|
10
|
-
@next_promise = next_promise
|
11
|
-
@next_promise.source = self
|
12
|
-
end
|
13
|
-
|
14
|
-
def fulfill(value)
|
15
|
-
if @on_fulfill
|
16
|
-
call_block(@on_fulfill, value)
|
17
|
-
else
|
18
|
-
@next_promise.fulfill(value)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def reject(reason)
|
23
|
-
if @on_reject
|
24
|
-
call_block(@on_reject, reason)
|
25
|
-
else
|
26
|
-
@next_promise.reject(reason)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def wait
|
31
|
-
source.wait
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def call_block(block, param)
|
37
|
-
@next_promise.fulfill(block.call(param))
|
38
|
-
rescue => ex
|
39
|
-
@next_promise.reject(ex)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
private_constant :Callback
|
43
|
-
end
|