a-ruby-promise 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/a-ruby-promise.rb +1 -0
- data/lib/promise.rb +95 -0
- data/lib/promise/version.rb +3 -0
- data/spec/aplus_spec.rb +46 -0
- data/spec/deferred.rb +16 -0
- data/spec/promise_spec.rb +50 -0
- data/spec/promises_aplus.rb +58 -0
- data/spec/promises_aplus/2_1_2_spec.rb +57 -0
- data/spec/promises_aplus/2_1_3_spec.rb +76 -0
- data/spec/promises_aplus/2_2_1_spec.rb +49 -0
- data/spec/promises_aplus/2_2_2_spec.rb +156 -0
- data/spec/promises_aplus/2_2_3_spec.rb +142 -0
- data/spec/promises_aplus/2_2_4_spec.rb +188 -0
- data/spec/promises_aplus/2_2_5_spec.rb +7 -0
- data/spec/promises_aplus/2_2_6_spec.rb +238 -0
- data/spec/promises_aplus/2_2_7_spec.rb +99 -0
- data/spec/spec_helper.rb +44 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d5e232579789fc7da3951b73e4eaf841cade3a27
|
4
|
+
data.tar.gz: 5abeb4887d52eb58764b13f6ec793cc9299a6382
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72bd28e4bc5cc0621bf7a7203a089d6e5006444c8c11ed2a994f7094536217d2f081fd26d6ab2562c727431c82aea400a75b4658133ef7712c54c09c4553021d
|
7
|
+
data.tar.gz: 98d4668c57d5b47f257cdf01461e106aa527f58c707d72ba775e91eee30942ae871ff4cf66ea4d4f5f784119ee9a38913d93b1c8f1e50060b40eb1c2bc736bfc
|
@@ -0,0 +1 @@
|
|
1
|
+
require "promise"
|
data/lib/promise.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "promise/version"
|
2
|
+
|
3
|
+
class Promise
|
4
|
+
|
5
|
+
attr_reader :state, :value, :reason
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@state = :pending
|
9
|
+
@value = nil
|
10
|
+
@reason = nil
|
11
|
+
@callbacks = []
|
12
|
+
@errbacks = []
|
13
|
+
instance_eval(&block) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[pending fulfilled rejected].each do |state|
|
17
|
+
define_method("#{state}?") { @state == state.to_sym }
|
18
|
+
end
|
19
|
+
|
20
|
+
def then(on_fulfilled = nil, on_rejected = nil)
|
21
|
+
result = Promise.new
|
22
|
+
|
23
|
+
call_and_fulfill = ->(value) {
|
24
|
+
begin
|
25
|
+
if function?(on_fulfilled)
|
26
|
+
new_value = on_fulfilled.call(value)
|
27
|
+
if Promise === new_value
|
28
|
+
new_value.then(->(v) { result.fulfill(v) }, ->(r) { result.reject(r) })
|
29
|
+
else
|
30
|
+
result.fulfill(new_value)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
result.fulfill(value)
|
34
|
+
end
|
35
|
+
rescue Exception => e
|
36
|
+
result.reject(e)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
|
40
|
+
call_and_reject = ->(reason) {
|
41
|
+
begin
|
42
|
+
if function?(on_rejected)
|
43
|
+
new_value = on_rejected.call(reason)
|
44
|
+
if Promise === new_value
|
45
|
+
new_value.then(->(v) { result.fulfill(v) }, ->(r) { result.reject(r) })
|
46
|
+
else
|
47
|
+
result.fulfill(new_value)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
result.reject(reason)
|
51
|
+
end
|
52
|
+
rescue Exception => e
|
53
|
+
result.reject(e)
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
case @state
|
58
|
+
when :pending
|
59
|
+
@callbacks << call_and_fulfill
|
60
|
+
@errbacks << call_and_reject
|
61
|
+
when :fulfilled
|
62
|
+
call_and_fulfill.call(@value)
|
63
|
+
when :rejected
|
64
|
+
call_and_reject.call(@reason)
|
65
|
+
end
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def fulfill(value)
|
73
|
+
return unless @state == :pending
|
74
|
+
@state = :fulfilled
|
75
|
+
@value = value
|
76
|
+
while callback = @callbacks.shift
|
77
|
+
callback.call(value) rescue nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def reject(reason)
|
82
|
+
return unless @state == :pending
|
83
|
+
@state = :rejected
|
84
|
+
@reason = reason
|
85
|
+
while errback = @errbacks.shift
|
86
|
+
errback.call(reason) rescue nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def function?(obj)
|
93
|
+
obj.respond_to? :call
|
94
|
+
end
|
95
|
+
end
|
data/spec/aplus_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
# Test suite from the `aplus` Python Promise implementation.
|
4
|
+
# https://github.com/xogeny/aplus#testing
|
5
|
+
|
6
|
+
describe Promise do
|
7
|
+
describe "Handles the case where the arguments to then are not functions or promises." do
|
8
|
+
it "3.2.6.4 pending" do
|
9
|
+
d = deferred
|
10
|
+
p1 = d.promise
|
11
|
+
p2 = p1.then(5)
|
12
|
+
d.fulfill(10)
|
13
|
+
p1.value.must_equal 10
|
14
|
+
p2.state.must_equal :fulfilled
|
15
|
+
p2.value.must_equal 10
|
16
|
+
end
|
17
|
+
|
18
|
+
it "3.2.6.4 fulfilled" do
|
19
|
+
p1 = resolved(10)
|
20
|
+
p2 = p1.then(5)
|
21
|
+
p1.value.must_equal 10
|
22
|
+
p2.state.must_equal :fulfilled
|
23
|
+
p2.value.must_equal 10
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "Handles the case where the arguments to then are values, not functions or promises." do
|
28
|
+
it "3.2.6.5 pending" do
|
29
|
+
d = deferred
|
30
|
+
p1 = d.promise
|
31
|
+
p2 = p1.then(nil, 5)
|
32
|
+
d.reject("Error")
|
33
|
+
p1.reason.must_equal "Error"
|
34
|
+
p2.state.must_equal :rejected
|
35
|
+
p2.reason.must_equal "Error"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "3.2.6.5 rejected" do
|
39
|
+
p1 = rejected("Error")
|
40
|
+
p2 = p1.then(nil, 5)
|
41
|
+
p1.reason.must_equal "Error"
|
42
|
+
p2.state.must_equal :rejected
|
43
|
+
p2.reason.must_equal "Error"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/deferred.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Deferred
|
2
|
+
attr_reader :promise
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@promise = Promise.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def fulfill(value)
|
9
|
+
@promise.__send__(:fulfill, value)
|
10
|
+
end
|
11
|
+
alias_method :resolve, :fulfill
|
12
|
+
|
13
|
+
def reject(reason)
|
14
|
+
@promise.__send__(:reject, reason)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
def timeout_promise(promise, timeout_in_seconds)
|
4
|
+
Promise.new do
|
5
|
+
promise.then method(:fulfill)
|
6
|
+
Thread.new do
|
7
|
+
sleep timeout_in_seconds
|
8
|
+
reject "Timeout reached"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Promise do
|
14
|
+
describe "simple fulfillment" do
|
15
|
+
it "must call success handler after fulfill" do
|
16
|
+
v = nil
|
17
|
+
d = deferred
|
18
|
+
p = d.promise
|
19
|
+
p.then ->(value) { v = value }
|
20
|
+
v.must_equal nil
|
21
|
+
d.fulfill(42)
|
22
|
+
v.must_equal 42
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "timeout" do
|
27
|
+
it "fulfills quickly" do
|
28
|
+
d = deferred
|
29
|
+
p1 = d.promise
|
30
|
+
p2 = timeout_promise(p1, 0.05)
|
31
|
+
d.fulfill "ok"
|
32
|
+
p2.state.must_equal :fulfilled
|
33
|
+
p2_value = nil
|
34
|
+
p2.then ->(value) { p2_value = value }
|
35
|
+
p2_value.must_equal "ok"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "rejects after some time" do
|
39
|
+
d = deferred
|
40
|
+
p1 = d.promise
|
41
|
+
p2 = timeout_promise(p1, 0.05)
|
42
|
+
sleep 0.1
|
43
|
+
d.fulfill "ok"
|
44
|
+
p2.state.must_equal :rejected
|
45
|
+
p2_reason = nil
|
46
|
+
p2.then nil, ->(reason) { p2_reason = reason }
|
47
|
+
p2_reason.must_equal "Timeout reached"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# http://promises-aplus.github.io/promises-spec/
|
2
|
+
# https://github.com/promises-aplus/promises-tests
|
3
|
+
module PromisesAplus
|
4
|
+
def setup
|
5
|
+
@done = false
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
time_limit = Time.now + 2
|
10
|
+
loop do
|
11
|
+
return if @done == true
|
12
|
+
raise Minitest::Assertion.new("`done` was never called") if Time.now >= time_limit
|
13
|
+
sleep 0.05
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def done!
|
18
|
+
@done = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.test_fulfilled(base, value, &test)
|
22
|
+
base.it "already-fulfilled" do
|
23
|
+
test.call(resolved(value), method(:done!))
|
24
|
+
end
|
25
|
+
|
26
|
+
base.it "immediately-fulfilled" do
|
27
|
+
d = deferred()
|
28
|
+
test.call(d.promise, method(:done!))
|
29
|
+
d.resolve(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
base.it "eventually-fulfilled" do
|
33
|
+
d = deferred()
|
34
|
+
test.call(d.promise, method(:done!))
|
35
|
+
short_sleep
|
36
|
+
d.resolve(value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.test_rejected(base, reason, &test)
|
41
|
+
base.it "already-rejected" do
|
42
|
+
test.call(rejected(reason), method(:done!))
|
43
|
+
end
|
44
|
+
|
45
|
+
base.it "immediately-rejected" do
|
46
|
+
d = deferred
|
47
|
+
test.call(d.promise, method(:done!))
|
48
|
+
d.reject(reason)
|
49
|
+
end
|
50
|
+
|
51
|
+
base.it "eventually-rejected" do
|
52
|
+
d = deferred
|
53
|
+
test.call(d.promise, method(:done!))
|
54
|
+
short_sleep
|
55
|
+
d.reject(reason)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative "../spec_helper"
|
3
|
+
require_relative "../promises_aplus"
|
4
|
+
|
5
|
+
dummy = { dummy: "dummy" }
|
6
|
+
|
7
|
+
describe "2.1.2.1: When fulfilled, a promise: must not transition to any other state." do
|
8
|
+
include PromisesAplus
|
9
|
+
|
10
|
+
PromisesAplus.test_fulfilled(self, dummy) do |promise, done|
|
11
|
+
on_fulfilled_called = false
|
12
|
+
|
13
|
+
promise.then ->(v) {
|
14
|
+
onFulfilledCalled = true
|
15
|
+
}, ->(r) {
|
16
|
+
on_fulfilled_called.must_equal false
|
17
|
+
done.call
|
18
|
+
}
|
19
|
+
|
20
|
+
short_sleep and done.call
|
21
|
+
end
|
22
|
+
|
23
|
+
it "trying to fulfill then immediately reject" do
|
24
|
+
d = deferred
|
25
|
+
on_fulfilled_called = false
|
26
|
+
d.promise.then ->(v) {
|
27
|
+
on_fulfilled_called = true
|
28
|
+
}, ->(r) {
|
29
|
+
on_fulfilled_called.must_equal false
|
30
|
+
done!
|
31
|
+
}
|
32
|
+
d.resolve(dummy)
|
33
|
+
d.reject(dummy)
|
34
|
+
|
35
|
+
short_sleep and done!
|
36
|
+
end
|
37
|
+
|
38
|
+
it "trying to fulfill then reject, delayed" do
|
39
|
+
d = deferred
|
40
|
+
on_fulfilled_called = false
|
41
|
+
|
42
|
+
d.promise.then ->(v) {
|
43
|
+
on_fulfilled_called = true
|
44
|
+
}, ->(r) {
|
45
|
+
on_fulfilled_called.must_equal false
|
46
|
+
done!
|
47
|
+
}
|
48
|
+
|
49
|
+
d.resolve(dummy)
|
50
|
+
Thread.new do
|
51
|
+
short_sleep
|
52
|
+
d.reject(dummy)
|
53
|
+
end
|
54
|
+
|
55
|
+
2.times { short_sleep } and done!
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative "../spec_helper"
|
3
|
+
require_relative "../promises_aplus"
|
4
|
+
|
5
|
+
dummy = { dummy: "dummy" } # we fulfill or reject with this when we don't intend to test against it
|
6
|
+
|
7
|
+
describe "2.1.3.1: When rejected, a promise: must not transition to any other state." do
|
8
|
+
include PromisesAplus
|
9
|
+
|
10
|
+
PromisesAplus.test_rejected(self, dummy) do |promise, done|
|
11
|
+
on_rejected_called = false
|
12
|
+
|
13
|
+
promise.then ->(v) {
|
14
|
+
on_rejected_called.must_equal false
|
15
|
+
done.call
|
16
|
+
}, ->(r) {
|
17
|
+
on_rejected_called = true
|
18
|
+
}
|
19
|
+
|
20
|
+
short_sleep and done.call
|
21
|
+
end
|
22
|
+
|
23
|
+
it "trying to reject then immediately fulfill" do
|
24
|
+
d = deferred
|
25
|
+
on_rejected_called = false
|
26
|
+
|
27
|
+
d.promise.then ->(v) {
|
28
|
+
on_rejected_called.must_equal false
|
29
|
+
done!
|
30
|
+
}, ->(r) {
|
31
|
+
on_rejected_called = true
|
32
|
+
}
|
33
|
+
|
34
|
+
d.reject(dummy)
|
35
|
+
d.resolve(dummy)
|
36
|
+
short_sleep and done!
|
37
|
+
end
|
38
|
+
|
39
|
+
it "trying to reject then fulfill, delayed" do
|
40
|
+
d = deferred
|
41
|
+
on_rejected_called = false
|
42
|
+
|
43
|
+
d.promise.then ->(v) {
|
44
|
+
on_rejected_called.must_equal false
|
45
|
+
done!
|
46
|
+
}, ->(r) {
|
47
|
+
on_rejected_called = true
|
48
|
+
}
|
49
|
+
|
50
|
+
Thread.new do
|
51
|
+
short_sleep
|
52
|
+
d.reject(dummy)
|
53
|
+
d.resolve(dummy)
|
54
|
+
end
|
55
|
+
2.times { short_sleep } and done!
|
56
|
+
end
|
57
|
+
|
58
|
+
it "trying to reject immediately then fulfill delayed" do
|
59
|
+
d = deferred
|
60
|
+
on_rejected_called = false
|
61
|
+
|
62
|
+
d.promise.then ->(v) {
|
63
|
+
on_rejected_called.must_equal false
|
64
|
+
done!
|
65
|
+
}, ->(r) {
|
66
|
+
on_rejected_called = true
|
67
|
+
}
|
68
|
+
|
69
|
+
d.reject(dummy)
|
70
|
+
Thread.new do
|
71
|
+
short_sleep
|
72
|
+
d.resolve(dummy)
|
73
|
+
end
|
74
|
+
2.times { short_sleep } and done!
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative "../spec_helper"
|
3
|
+
require_relative "../promises_aplus"
|
4
|
+
|
5
|
+
dummy = { dummy: "dummy" }
|
6
|
+
|
7
|
+
describe "2.2.1: Both `onFulfilled` and `onRejected` are optional arguments." do
|
8
|
+
include PromisesAplus
|
9
|
+
|
10
|
+
describe "2.2.1.1: If `onFulfilled` is not a function, it must be ignored." do
|
11
|
+
[nil, false, 5, Object.new].each do |non_function|
|
12
|
+
describe "applied to a directly-rejected promise" do
|
13
|
+
it "`onFulfilled` is `#{non_function.inspect}`" do
|
14
|
+
rejected(dummy).then(non_function, ->(r) {
|
15
|
+
done!
|
16
|
+
})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "applied to a promise rejected and then chained off of" do
|
21
|
+
it "`onFulfilled` is #{non_function.inspect}" do
|
22
|
+
rejected(dummy).then(->(v) { }, nil).then(non_function, ->(r) {
|
23
|
+
done!
|
24
|
+
})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "2.2.1.2: If `onRejected` is not a function, it must be ignored." do
|
31
|
+
[nil, false, 5, Object.new].each do |non_function|
|
32
|
+
describe "applied to a directly-fulfilled promise" do
|
33
|
+
it "`onRejected` is `#{non_function.inspect}`" do
|
34
|
+
resolved(dummy).then(->(v) {
|
35
|
+
done!
|
36
|
+
}, non_function)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "applied to a promise fulfilled and then chained off of" do
|
41
|
+
it "`onRejected` is `#{non_function.inspect}`" do
|
42
|
+
resolved(dummy).then(nil, ->(r) { }).then(->(v) {
|
43
|
+
done!
|
44
|
+
}, non_function)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|