Skip to content

Commit 9b67f08

Browse files
committedDec 25, 2013
Merge pull request #464 from opal/promise
Implement promises
2 parents abb78c1 + 86a79c9 commit 9b67f08

File tree

7 files changed

+392
-0
lines changed

7 files changed

+392
-0
lines changed
 
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require 'promise'
2+
3+
describe 'Promise#collect' do
4+
it 'calls the block with all the previous results' do
5+
x = 42
6+
7+
Promise.value(1).then { 2 }.then { 3 }.collect {|a, b, c|
8+
x = a + b + c
9+
}
10+
11+
x.should == 6
12+
end
13+
14+
it 'calls the then after the collect' do
15+
x = 42
16+
17+
Promise.value(1).then { 2 }.then { 3 }.collect {|a, b, c|
18+
a + b + c
19+
}.then { |v| x = v }
20+
21+
x.should == 6
22+
end
23+
24+
it 'works after a when' do
25+
x = 42
26+
27+
Promise.value(1).then {
28+
Promise.when Promise.value(2), Promise.value(3)
29+
}.collect {|a, b|
30+
x = a + b[0] + b[1]
31+
}
32+
33+
x.should == 6
34+
end
35+
end
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
require 'promise'
2+
3+
describe 'Promise.error' do
4+
it 'rejects the promise with the given error' do
5+
Promise.error(23).error.should == 23
6+
end
7+
8+
it 'marks the promise as realized' do
9+
Promise.error(23).realized?.should be_true
10+
end
11+
12+
it 'marks the promise as rejected' do
13+
Promise.error(23).rejected?.should be_true
14+
end
15+
end
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require 'promise'
2+
3+
describe 'Promise#rescue' do
4+
it 'calls the block when the promise has already been rejected' do
5+
x = 42
6+
Promise.error(23).rescue { |v| x = v }
7+
x.should == 23
8+
end
9+
10+
it 'calls the block when the promise is rejected' do
11+
a = Promise.new
12+
x = 42
13+
14+
a.rescue { |v| x = v }
15+
a.reject(23)
16+
17+
x.should == 23
18+
end
19+
20+
it 'does not call then blocks when the promise is rejected' do
21+
x = 42
22+
y = 23
23+
24+
Promise.error(23).then { y = 42 }.rescue { |v| x = v }
25+
26+
x.should == 23
27+
y.should == 23
28+
end
29+
30+
it 'does not call subsequent rescue blocks' do
31+
x = 42
32+
Promise.error(23).rescue { |v| x = v }.rescue { x = 42 }
33+
x.should == 23
34+
end
35+
end

‎spec/opal/stdlib/promise/then_spec.rb

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require 'promise'
2+
3+
describe 'Promise#then' do
4+
it 'calls the block when the promise has already been resolved' do
5+
x = 42
6+
Promise.value(23).then { |v| x = v }
7+
x.should == 23
8+
end
9+
10+
it 'calls the block when the promise is resolved' do
11+
a = Promise.new
12+
x = 42
13+
14+
a.then { |v| x = v }
15+
a.resolve(23)
16+
17+
x.should == 23
18+
end
19+
20+
it 'works with multiple chains' do
21+
x = 42
22+
Promise.value(2).then { |v| v * 2 }.then { |v| v * 4 }.then { |v| x = v }
23+
x.should == 16
24+
end
25+
26+
it 'works when a block returns a promise' do
27+
a = Promise.new
28+
b = Promise.new
29+
30+
x = 42
31+
a.then { b }.then { |v| x = v }
32+
33+
a.resolve(42)
34+
b.resolve(23)
35+
36+
x.should == 23
37+
end
38+
end
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
require 'promise'
2+
3+
describe 'Promise.value' do
4+
it 'resolves the promise with the given value' do
5+
Promise.value(23).value.should == 23
6+
end
7+
8+
it 'marks the promise as realized' do
9+
Promise.value(23).realized?.should be_true
10+
end
11+
12+
it 'marks the promise as resolved' do
13+
Promise.value(23).resolved?.should be_true
14+
end
15+
end

‎spec/opal/stdlib/promise/when_spec.rb

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'promise'
2+
3+
describe 'Promise.when' do
4+
it 'calls the block with all promises results' do
5+
a = Promise.new
6+
b = Promise.new
7+
8+
x = 42
9+
10+
Promise.when(a, b).then {|y, z|
11+
x = y + z
12+
}
13+
14+
a.resolve(1)
15+
b.resolve(2)
16+
17+
x.should == 3
18+
end
19+
end

‎stdlib/promise.rb

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
class Promise
2+
def self.value(value)
3+
new.resolve(value)
4+
end
5+
6+
def self.error(value)
7+
new.reject(value)
8+
end
9+
10+
def self.when(*promises)
11+
When.new(promises.flatten)
12+
end
13+
14+
attr_reader :value, :error, :prev, :next
15+
16+
def initialize(success = nil, failure = nil)
17+
@success = success
18+
@failure = failure
19+
20+
@realized = nil
21+
@value = nil
22+
@error = nil
23+
@delayed = nil
24+
25+
@prev = nil
26+
@next = nil
27+
end
28+
29+
def act?
30+
@success != nil
31+
end
32+
33+
def realized?
34+
@realized != nil
35+
end
36+
37+
def resolved?
38+
@realized == :value
39+
end
40+
41+
def rejected?
42+
@realized == :error
43+
end
44+
45+
def ^(promise)
46+
promise << self
47+
self >> promise
48+
49+
promise
50+
end
51+
52+
def <<(promise)
53+
@prev = promise
54+
55+
self
56+
end
57+
58+
def >>(promise)
59+
@next = promise
60+
61+
if resolved?
62+
promise.resolve(@delayed || value)
63+
elsif rejected? && (!@failure || Promise === (@delayed || @error))
64+
promise.reject(@delayed || error)
65+
end
66+
67+
self
68+
end
69+
70+
def resolve(value)
71+
if realized?
72+
raise ArgumentError, 'the promise has already been realized'
73+
end
74+
75+
if Promise === value
76+
value << @prev
77+
78+
return value ^ self
79+
end
80+
81+
@realized = :value
82+
@value = value
83+
84+
if @success
85+
value = @success.call(value)
86+
end
87+
88+
if @next
89+
@next.resolve(value)
90+
else
91+
@delayed = value
92+
end
93+
94+
self
95+
end
96+
97+
def reject(value)
98+
if realized?
99+
raise ArgumentError, 'the promise has already been realized'
100+
end
101+
102+
if Promise === value
103+
value << @prev
104+
105+
return value ^ self
106+
end
107+
108+
@realized = :error
109+
@error = value
110+
111+
if @failure
112+
value = @failure.call(value)
113+
114+
if Promise === value
115+
if @next
116+
@next.reject(value)
117+
else
118+
@delayed = value
119+
end
120+
end
121+
else
122+
if @next
123+
@next.reject(value)
124+
else
125+
@delayed = value
126+
end
127+
end
128+
129+
self
130+
end
131+
132+
def then(&block)
133+
self ^ Promise.new(block)
134+
end
135+
136+
alias do then
137+
138+
def fail(&block)
139+
self ^ Promise.new(nil, block)
140+
end
141+
142+
alias rescue fail
143+
alias catch fail
144+
145+
def always(&block)
146+
self ^ Promise.new(block, block)
147+
end
148+
149+
alias finally always
150+
alias ensure always
151+
152+
def collect(&block)
153+
self ^ Collect.new(block)
154+
end
155+
156+
def inspect
157+
result = "#<#{self.class}(#{object_id})"
158+
159+
if @next
160+
result += " >> #{@next.inspect}"
161+
end
162+
163+
if realized?
164+
result += ": #{(@value || @error).inspect}>"
165+
else
166+
result += ">"
167+
end
168+
169+
result
170+
end
171+
172+
class Collect < self
173+
def self.it(promise)
174+
unless promise.realized?
175+
raise ArgumentError, "the promise hasn't been realized"
176+
end
177+
178+
current = promise.act? ? [promise.value] : []
179+
180+
if prev = promise.prev
181+
current.concat(it(prev))
182+
else
183+
current
184+
end
185+
end
186+
187+
def initialize(block)
188+
super -> {
189+
block.call(*Collect.it(self).reverse)
190+
}
191+
end
192+
end
193+
194+
class When < self
195+
def initialize(promises = [])
196+
super()
197+
198+
@wait = []
199+
200+
promises.each {|promise|
201+
wait promise
202+
}
203+
end
204+
205+
def wait(promise)
206+
@wait << promise
207+
208+
promise.always {
209+
try if @next
210+
}
211+
212+
self
213+
end
214+
215+
def >>(*)
216+
super.tap {
217+
try
218+
}
219+
end
220+
221+
def try
222+
if @wait.all?(&:realized?)
223+
if promise = @wait.find(&:rejected?)
224+
reject(promise.error)
225+
else
226+
resolve(@wait.map(&:value))
227+
end
228+
end
229+
end
230+
231+
def and(&block)
232+
wait Promise.new(block)
233+
end
234+
end
235+
end

0 commit comments

Comments
 (0)
Please sign in to comment.