promise.rb 0.7.3 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|