a-ruby-promise 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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