promise.rb 0.7.1 → 0.7.2

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: c7dee00af5f45fbb9d9d19bf97268b908b0134d2
4
- data.tar.gz: 75cc57634668e4d7f0b0f0e8200b89b46ecd04e0
3
+ metadata.gz: d1c5730ef1d6a9d4aca9152d2d0b0978d3599bf9
4
+ data.tar.gz: 7bf7f65107907530efb5befd130d8257c95b1ebd
5
5
  SHA512:
6
- metadata.gz: a825e41f6f0c09abacfca43c33cb9aaf7715f32f6a3de6d73b5733db48808d55231155f9490a80e54a64d063afad1227a90e7bb3f4cb2b3a6d3e6c6019312275
7
- data.tar.gz: 43cc9b875210365d96cf180ad306da81d18bb9dd38a66411f8b8ce3fbf9385d2687463f1d5ab1737d45763d2a2d953c44979cd9efe023ce3e9f9ea413ae6f4d6
6
+ metadata.gz: b91773b745efae988afbcdd56080fac09398f72e781266e92b755128a3f606fc09f63299849a729c2b6e1ced9cc1cdc46581beb6880949c7ea2748516994cff3
7
+ data.tar.gz: 7ca9447a4c2766299ccc9d941dd2a25d68a65a1626ff3407726966ed85157bc0c25873bf53c5eecd2a6e683c093a6217f354be5712c38602d5d02f7e7f8bb3c5
@@ -6,7 +6,6 @@ rvm:
6
6
  - 2.1
7
7
  - 2.2
8
8
  - jruby
9
- - rbx-3.14
10
9
  matrix:
11
10
  fast_finish: true
12
11
  before_install:
@@ -1,10 +1,17 @@
1
1
  # promise.rb changelog
2
2
 
3
+ ## 0.7.2 (November 15, 2016)
4
+
5
+ ### Features
6
+
7
+ * Add support for calling sync on the result of Promise.all (pull #24)
8
+ * Add Promise.sync to unwrap an object that may be a promise. (#25)
9
+
3
10
  ## 0.7.1 (June 15, 2016)
4
11
 
5
12
  ### Features
6
13
 
7
- * Add Promise.map_value for chaining a promise or plain value
14
+ * Add Promise.map_value for chaining a promise or plain value (pull #17)
8
15
 
9
16
  ## 0.7.0 (February 24, 2016)
10
17
 
data/Gemfile CHANGED
@@ -4,6 +4,12 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
+ if Gem.ruby_version < Gem::Version.new('2.0')
8
+ # gems that no longer support ruby 1.9.3
9
+ gem 'json', '~> 1.8.3'
10
+ gem 'tins', '~> 1.6.0'
11
+ gem 'term-ansicolor', '~> 1.3.2'
12
+ end
7
13
  if Gem.ruby_version >= Gem::Version.new('2.1')
8
14
  gem 'devtools', '~> 0.1.4'
9
15
  end
@@ -11,7 +17,7 @@ gem 'fuubar', '~> 2.0.0'
11
17
  gem 'awesome_print'
12
18
 
13
19
  gem 'rake'
14
- gem 'rspec', '~> 3.4.0'
20
+ gem 'rspec', '~> 3.5'
15
21
  gem 'rspec-its'
16
22
  gem 'coveralls', '~> 0.8.9'
17
23
 
data/README.md CHANGED
@@ -6,7 +6,7 @@ tested on MRI 1.9, 2.0, 2.1, 2.2, Rubinius, and JRuby.
6
6
 
7
7
  Similar projects:
8
8
 
9
- - [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby), Promises/A(+) implementation, thread based
9
+ - [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby), Promises/A(+) inspired implementation, thread based
10
10
  - [ruby-thread](https://github.com/meh/ruby-thread), thread/mutex/condition variable based, thread safe
11
11
  - [promise](https://github.com/bhuga/promising-future), a.k.a. promising-future, classic promises and futures, thread based
12
12
  - [celluloid-promise](https://github.com/cotag/celluloid-promise), inspired by Q, backed by a Celluloid actor
@@ -52,7 +52,7 @@ NilCheck:
52
52
  RepeatedConditional:
53
53
  enabled: true
54
54
  exclude: []
55
- max_ifs: 3
55
+ max_ifs: 4
56
56
  TooManyInstanceVariables:
57
57
  enabled: true
58
58
  exclude: []
@@ -64,7 +64,7 @@ TooManyStatements:
64
64
  exclude:
65
65
  - initialize
66
66
  - each
67
- max_statements: 5
67
+ max_statements: 6
68
68
  UncommunicativeMethodName:
69
69
  enabled: true
70
70
  exclude: []
@@ -99,3 +99,6 @@ GuardClause:
99
99
 
100
100
  Alias:
101
101
  EnforcedStyle: prefer_alias_method
102
+
103
+ Metrics/ClassLength:
104
+ Enabled: false
@@ -8,9 +8,11 @@ require 'promise/group'
8
8
 
9
9
  class Promise
10
10
  Error = Class.new(RuntimeError)
11
+ BrokenError = Class.new(Error)
11
12
 
12
13
  include Promise::Progress
13
14
 
15
+ attr_accessor :source
14
16
  attr_reader :state, :value, :reason
15
17
 
16
18
  def self.resolve(obj)
@@ -30,6 +32,10 @@ class Promise
30
32
  end
31
33
  end
32
34
 
35
+ def self.sync(obj)
36
+ obj.is_a?(Promise) ? obj.sync : obj
37
+ end
38
+
33
39
  def initialize
34
40
  @state = :pending
35
41
  @callbacks = []
@@ -51,7 +57,7 @@ class Promise
51
57
  on_fulfill ||= block
52
58
  next_promise = self.class.new
53
59
 
54
- add_callback(Callback.new(self, on_fulfill, on_reject, next_promise))
60
+ add_callback(Callback.new(on_fulfill, on_reject, next_promise))
55
61
  next_promise
56
62
  end
57
63
 
@@ -61,17 +67,21 @@ class Promise
61
67
  alias_method :catch, :rescue
62
68
 
63
69
  def sync
64
- wait if pending?
70
+ if pending?
71
+ wait
72
+ raise BrokenError if pending?
73
+ end
65
74
  raise reason if rejected?
66
75
  value
67
76
  end
68
77
 
69
78
  def fulfill(value = nil)
70
79
  if Promise === value
71
- Callback.assume_state(value, self)
80
+ value.add_callback(self)
72
81
  else
73
82
  dispatch do
74
83
  @state = :fulfilled
84
+ @source = nil
75
85
  @value = value
76
86
  end
77
87
  end
@@ -81,14 +91,37 @@ class Promise
81
91
  def reject(reason = nil)
82
92
  dispatch do
83
93
  @state = :rejected
94
+ @source = nil
84
95
  @reason = reason_coercion(reason || Error)
85
96
  end
86
97
  end
87
98
 
99
+ # Override to support sync on a promise without a source or to wait
100
+ # for deferred callbacks on the source
101
+ def wait
102
+ while source
103
+ saved_source = source
104
+ saved_source.wait
105
+ break if saved_source.equal?(source)
106
+ end
107
+ end
108
+
109
+ protected
110
+
111
+ # Override to defer calling the callback for Promises/A+ spec compliance
88
112
  def defer
89
113
  yield
90
114
  end
91
115
 
116
+ def add_callback(callback)
117
+ if pending?
118
+ @callbacks << callback
119
+ callback.source = self
120
+ else
121
+ dispatch!(callback)
122
+ end
123
+ end
124
+
92
125
  private
93
126
 
94
127
  def reason_coercion(reason)
@@ -101,14 +134,6 @@ class Promise
101
134
  reason
102
135
  end
103
136
 
104
- def add_callback(callback)
105
- if pending?
106
- @callbacks << callback
107
- else
108
- dispatch!(callback)
109
- end
110
- end
111
-
112
137
  def dispatch
113
138
  if pending?
114
139
  yield
@@ -118,6 +143,12 @@ class Promise
118
143
  end
119
144
 
120
145
  def dispatch!(callback)
121
- defer { callback.call }
146
+ defer do
147
+ if fulfilled?
148
+ callback.fulfill(value)
149
+ else
150
+ callback.reject(reason)
151
+ end
152
+ end
122
153
  end
123
154
  end
@@ -2,38 +2,42 @@
2
2
 
3
3
  class Promise
4
4
  class Callback
5
- def self.assume_state(source, target)
6
- on_fulfill = target.method(:fulfill)
7
- on_reject = target.method(:reject)
8
- source.then(on_fulfill, on_reject)
9
- end
5
+ attr_accessor :source
10
6
 
11
- def initialize(promise, on_fulfill, on_reject, next_promise)
12
- @promise = promise
7
+ def initialize(on_fulfill, on_reject, next_promise)
13
8
  @on_fulfill = on_fulfill
14
9
  @on_reject = on_reject
15
10
  @next_promise = next_promise
11
+ @next_promise.source = self
16
12
  end
17
13
 
18
- def call
19
- if @promise.fulfilled?
20
- call_block(@on_fulfill, @promise.value)
14
+ def fulfill(value)
15
+ if @on_fulfill
16
+ call_block(@on_fulfill, value)
21
17
  else
22
- call_block(@on_reject, @promise.reason)
18
+ @next_promise.fulfill(value)
23
19
  end
24
20
  end
25
21
 
26
- def call_block(block, param)
27
- if block
28
- begin
29
- @next_promise.fulfill(block.call(param))
30
- rescue => ex
31
- @next_promise.reject(ex)
32
- end
22
+ def reject(reason)
23
+ if @on_reject
24
+ call_block(@on_reject, reason)
33
25
  else
34
- self.class.assume_state(@promise, @next_promise)
26
+ @next_promise.reject(reason)
35
27
  end
36
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
37
41
  end
38
42
  private_constant :Callback
39
43
  end
@@ -1,5 +1,6 @@
1
1
  class Promise
2
2
  class Group
3
+ attr_accessor :source
3
4
  attr_reader :promise
4
5
 
5
6
  def initialize(result_promise, inputs)
@@ -9,15 +10,22 @@ class Promise
9
10
  if @remaining.zero?
10
11
  promise.fulfill(inputs)
11
12
  else
13
+ promise.source = self
12
14
  chain_inputs
13
15
  end
14
16
  end
15
17
 
18
+ def wait
19
+ each_promise do |input_promise|
20
+ input_promise.wait if input_promise.pending?
21
+ end
22
+ end
23
+
16
24
  private
17
25
 
18
26
  def chain_inputs
19
27
  on_fulfill = method(:on_fulfill)
20
- on_reject = promise.method(:reject)
28
+ on_reject = promise.public_method(:reject)
21
29
  each_promise do |input_promise|
22
30
  input_promise.then(on_fulfill, on_reject)
23
31
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Promise
4
- VERSION = '0.7.1'.freeze
4
+ VERSION = '0.7.2'.freeze
5
5
  end
@@ -6,7 +6,7 @@ require 'promise/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'promise.rb'
9
- spec.version = Promise::VERSION
9
+ spec.version = Promise::VERSION.dup
10
10
  spec.authors = ['Lars Gierth']
11
11
  spec.email = ['lars.gierth@gmail.com']
12
12
  spec.description = %q{Promises/A+ for Ruby}
@@ -19,6 +19,7 @@ end
19
19
 
20
20
  require 'promise'
21
21
  require_relative 'support/delayed_promise'
22
+ require_relative 'support/promise_loader'
22
23
 
23
24
  require 'awesome_print'
24
25
  require 'devtools/spec_helper' if Gem.ruby_version >= Gem::Version.new('2.1')
@@ -0,0 +1,13 @@
1
+ class PromiseLoader
2
+ def self.lazy_load(promise, &block)
3
+ promise.source = new(&block)
4
+ end
5
+
6
+ def initialize(&block)
7
+ @block = block
8
+ end
9
+
10
+ def wait
11
+ @block.call
12
+ end
13
+ end
@@ -476,6 +476,58 @@ describe Promise do
476
476
  expect(subject).not_to receive(:wait)
477
477
  expect(subject.sync).to be(value)
478
478
  end
479
+
480
+ it 'waits for source by default' do
481
+ PromiseLoader.lazy_load(subject) { subject.fulfill(1) }
482
+ p2 = subject.then { |v| v + 1 }
483
+ expect(p2).to be_pending
484
+ expect(p2.sync).to eq(2)
485
+ expect(p2.source).to eq(nil)
486
+ end
487
+
488
+ it 'waits for source that is fulfilled with a promise' do
489
+ PromiseLoader.lazy_load(subject) { subject.fulfill(1) }
490
+ p2 = subject.then do |v|
491
+ Promise.new.tap do |p3|
492
+ PromiseLoader.lazy_load(p3) { p3.fulfill(v + 1) }
493
+ end
494
+ end
495
+ expect(p2).to be_pending
496
+ expect(p2.sync).to eq(2)
497
+ expect(p2.source).to eq(nil)
498
+ end
499
+
500
+ it 'waits for source rejection' do
501
+ PromiseLoader.lazy_load(subject) { subject.reject(reason) }
502
+ p2 = subject.then { |v| v + 1 }
503
+ expect { p2.sync }.to raise_error(reason)
504
+ expect(p2.source).to eq(nil)
505
+ end
506
+
507
+ it 'raises for promise without a source by default' do
508
+ expect { subject.sync }.to raise_error(Promise::BrokenError)
509
+ end
510
+
511
+ it 'raises if source.wait leaves promise pending' do
512
+ PromiseLoader.lazy_load(subject) {}
513
+ expect { subject.sync }.to raise_error(Promise::BrokenError)
514
+ end
515
+ end
516
+
517
+ describe '.sync' do
518
+ it 'returns non-promise argument' do
519
+ expect(Promise.sync(42)).to eq(42)
520
+ end
521
+
522
+ it 'calls sync on promise argument' do
523
+ PromiseLoader.lazy_load(subject) { subject.fulfill(123) }
524
+ expect(Promise.sync(subject)).to eq(123)
525
+ end
526
+
527
+ it 'calls sync on promise of another class' do
528
+ promise = Class.new(Promise).resolve('a')
529
+ expect(Class.new(Promise).sync(promise)).to eq('a')
530
+ end
479
531
  end
480
532
 
481
533
  describe '.resolve' do
@@ -575,6 +627,30 @@ describe Promise do
575
627
  p1.fulfill(1.0)
576
628
  expect(result.sync).to eq([1.0, 2])
577
629
  end
630
+
631
+ it 'returns a promise that can sync promises of another class' do
632
+ p1 = DelayedPromise.new
633
+ DelayedPromise.deferred << -> { p1.fulfill('a') }
634
+
635
+ result = Promise.all([p1, Promise.resolve(:b), 3])
636
+
637
+ expect(result).to be_pending
638
+ expect(result.sync).to eq(['a', :b, 3])
639
+ end
640
+
641
+ it 'sync on result does not call wait on resolved promises' do
642
+ p1 = Class.new(Promise) do
643
+ def wait
644
+ raise 'wait not expected'
645
+ end
646
+ end.resolve(:one)
647
+ p2 = DelayedPromise.new
648
+ DelayedPromise.deferred << -> { p2.fulfill(:two) }
649
+
650
+ result = Promise.all([p1, p2])
651
+
652
+ expect(result.sync).to eq([:one, :two])
653
+ end
578
654
  end
579
655
 
580
656
  describe '.map_value' do
metadata CHANGED
@@ -1,24 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promise.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Gierth
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-16 00:00:00.000000000 Z
11
+ date: 2016-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: rspec
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
17
  - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: '0'
19
- name: rspec
20
- prerelease: false
21
20
  type: :development
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
@@ -54,12 +54,13 @@ files:
54
54
  - promise.rb.gemspec
55
55
  - spec/spec_helper.rb
56
56
  - spec/support/delayed_promise.rb
57
+ - spec/support/promise_loader.rb
57
58
  - spec/unit/promise_spec.rb
58
59
  homepage: https://github.com/lgierth/promise
59
60
  licenses:
60
61
  - Public Domain
61
62
  metadata: {}
62
- post_install_message:
63
+ post_install_message:
63
64
  rdoc_options: []
64
65
  require_paths:
65
66
  - lib
@@ -74,13 +75,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  requirements: []
77
- rubyforge_project:
78
- rubygems_version: 2.4.8
79
- signing_key:
78
+ rubyforge_project:
79
+ rubygems_version: 2.5.1
80
+ signing_key:
80
81
  specification_version: 4
81
82
  summary: Ruby implementation of the Promises/A+ spec
82
83
  test_files:
83
84
  - spec/spec_helper.rb
84
85
  - spec/support/delayed_promise.rb
86
+ - spec/support/promise_loader.rb
85
87
  - spec/unit/promise_spec.rb
86
- has_rdoc: