promise.rb 0.7.1 → 0.7.2

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