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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e3533c32b2d839c7fe076eb9ef7e02e38d594cf
4
- data.tar.gz: 8041c55194508d424bf52709b0d55f97c75900a5
3
+ metadata.gz: 18cdca70f20edeb0b04ea3ca124ac37b9421e7e1
4
+ data.tar.gz: 34498e438956c5f0c6a9e689c8cd9b2ad56c5231
5
5
  SHA512:
6
- metadata.gz: b7d7ea80750476d1334d79b24c87d6b2c50a3165f336e978064eab63398722bdb5d652aac575ca9826d28305099780e40812ae9def94a23f729a657f345b09d9
7
- data.tar.gz: c079077407ed72f4d8b0e0ba688ec623f59a23e2e3ae4240209def4c9d54bfccc19a7105e21e484dc46a6db6fa79e7be7441f45e9ba9877f74a6d8ed68c58806
6
+ metadata.gz: 7dc20a5fb4be8e4fc352c243f03528a9458cce898051fcb3c2737f7b5a767de2049bdbb522122c6f938af17beaca055b715ca1edb94db968fc6e27f65eb15d28
7
+ data.tar.gz: 1c97f288a31cff1aa11ef0024026a7fca37818506a0ea804b7fb1fa80b546208e7bcb7b5e74fe4a37fa8a011992b14017ce13c6c0cae9ac7299fce076f540f42
@@ -5,6 +5,7 @@ rvm:
5
5
  - 2.0
6
6
  - 2.1
7
7
  - 2.2
8
+ - 2.4
8
9
  - jruby
9
10
  matrix:
10
11
  fast_finish: true
@@ -1,5 +1,11 @@
1
1
  # promise.rb changelog
2
2
 
3
+ ## 0.7.4 (August 25, 2017)
4
+
5
+ ### Features
6
+
7
+ * Add an observer API, used internally to improve performance (pull #34)
8
+
3
9
  ## 0.7.3 (April 28, 2017)
4
10
 
5
11
  ### Features
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- if Gem.ruby_version >= Gem::Version.new('2.1')
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
@@ -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
@@ -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
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 10.6
2
+ threshold: 15.8
@@ -33,7 +33,7 @@ IrresponsibleModule:
33
33
  LongParameterList:
34
34
  enabled: true
35
35
  exclude: []
36
- max_params: 2
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: true
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: true
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
@@ -14,7 +14,7 @@ ParameterLists:
14
14
 
15
15
  MethodLength:
16
16
  CountComments: false
17
- Max: 11
17
+ Max: 20
18
18
 
19
19
  # Avoid more than `Max` levels of nesting.
20
20
  BlockNesting:
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'promise/version'
4
4
 
5
- require 'promise/callback'
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
- add_callback(Callback.new(on_fulfill, on_reject, next_promise))
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
- if Promise === value
80
- value.add_callback(self)
81
- else
82
- dispatch do
83
- @state = :fulfilled
84
- @source = nil
85
- @value = value
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
- dispatch do
93
- @state = :rejected
94
- @source = nil
95
- @reason = reason_coercion(reason || Error)
96
- end
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 add_callback(callback)
118
- if pending?
119
- @callbacks << callback
120
- callback.source = self
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
- dispatch!(callback)
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 dispatch
139
- if pending?
140
- yield
141
- @callbacks.each { |callback| dispatch!(callback) }
142
- nil
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 dispatch!(callback)
202
+ def notify_rejection
147
203
  defer do
148
- if fulfilled?
149
- callback.fulfill(value)
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
@@ -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
- private
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Promise
4
- VERSION = '0.7.3'.freeze
4
+ VERSION = '0.7.4'.freeze
5
5
  end
@@ -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
@@ -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 or on_reject is called' do
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 'returns a fulfilled promise for an array with no promises' do
567
- obj = Object.new
568
- promise = Promise.all([1, 'b', obj])
569
- expect(promise.fulfilled?).to eq(true)
570
- expect(promise.value).to eq([1, 'b', obj])
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.3
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-04-28 00:00:00.000000000 Z
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
@@ -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