a-ruby-promise 0.0.1

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.
@@ -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"
@@ -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
@@ -0,0 +1,3 @@
1
+ class Promise
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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