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 +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +8 -1
- data/Gemfile +7 -1
- data/README.md +1 -1
- data/config/reek.yml +2 -2
- data/config/rubocop.yml +3 -0
- data/lib/promise.rb +43 -12
- data/lib/promise/callback.rb +23 -19
- data/lib/promise/group.rb +9 -1
- data/lib/promise/version.rb +1 -1
- data/promise.rb.gemspec +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/promise_loader.rb +13 -0
- data/spec/unit/promise_spec.rb +76 -0
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1c5730ef1d6a9d4aca9152d2d0b0978d3599bf9
|
4
|
+
data.tar.gz: 7bf7f65107907530efb5befd130d8257c95b1ebd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b91773b745efae988afbcdd56080fac09398f72e781266e92b755128a3f606fc09f63299849a729c2b6e1ced9cc1cdc46581beb6880949c7ea2748516994cff3
|
7
|
+
data.tar.gz: 7ca9447a4c2766299ccc9d941dd2a25d68a65a1626ff3407726966ed85157bc0c25873bf53c5eecd2a6e683c093a6217f354be5712c38602d5d02f7e7f8bb3c5
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -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.
|
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
|
data/config/reek.yml
CHANGED
@@ -52,7 +52,7 @@ NilCheck:
|
|
52
52
|
RepeatedConditional:
|
53
53
|
enabled: true
|
54
54
|
exclude: []
|
55
|
-
max_ifs:
|
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:
|
67
|
+
max_statements: 6
|
68
68
|
UncommunicativeMethodName:
|
69
69
|
enabled: true
|
70
70
|
exclude: []
|
data/config/rubocop.yml
CHANGED
data/lib/promise.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/promise/callback.rb
CHANGED
@@ -2,38 +2,42 @@
|
|
2
2
|
|
3
3
|
class Promise
|
4
4
|
class Callback
|
5
|
-
|
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(
|
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
|
19
|
-
if @
|
20
|
-
call_block(@on_fulfill,
|
14
|
+
def fulfill(value)
|
15
|
+
if @on_fulfill
|
16
|
+
call_block(@on_fulfill, value)
|
21
17
|
else
|
22
|
-
|
18
|
+
@next_promise.fulfill(value)
|
23
19
|
end
|
24
20
|
end
|
25
21
|
|
26
|
-
def
|
27
|
-
if
|
28
|
-
|
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
|
-
|
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
|
data/lib/promise/group.rb
CHANGED
@@ -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.
|
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
|
data/lib/promise/version.rb
CHANGED
data/promise.rb.gemspec
CHANGED
@@ -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}
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/promise_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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:
|