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 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