whenner 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9a4aa8af9fb47aa98f75a11bb2433eacf417ae87
4
+ data.tar.gz: 69d6cbba0ed8f7549028e185618321b31be8a1a0
5
+ SHA512:
6
+ metadata.gz: 33fe745591cd305f62158756d6de021c3bdb82b1501c73826a9e6a06010db311c4bfbf1f4234ea09680af99d3cea32391a0cfabf0e8b4f33f229ac65601f40b9
7
+ data.tar.gz: 97794e75339f30bf8de24b1325d51674fe96ee6a55a0e421ff17a54fdcc1e0b02fd97a21c0e1fa2ac28a0cb0b72dc17712fe55c45a424159043ba35f51708117
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg
2
+ .bundle
3
+ .yardoc
4
+ doc
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ --format progress
2
+ --color
3
+ --order rand
4
+ -Ilib
5
+ -Ispec
6
+ -r whenner
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --no-private
2
+ --title "Whenner"
3
+ --readme README.md
4
+ --markup markdown
5
+ --markup-provider kramdown
6
+ -
7
+ HISTORY.md
8
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ whenner (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ kramdown (1.3.0)
11
+ rake (10.1.0)
12
+ rspec (2.14.1)
13
+ rspec-core (~> 2.14.0)
14
+ rspec-expectations (~> 2.14.0)
15
+ rspec-mocks (~> 2.14.0)
16
+ rspec-core (2.14.7)
17
+ rspec-expectations (2.14.4)
18
+ diff-lcs (>= 1.1.3, < 2.0)
19
+ rspec-mocks (2.14.4)
20
+ yard (0.8.7.3)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ kramdown
27
+ rake
28
+ rspec
29
+ whenner!
30
+ yard
data/HISTORY.md ADDED
@@ -0,0 +1,6 @@
1
+ # HISTORY
2
+
3
+ ## 0.1.0
4
+
5
+ * Initial release
6
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2013 Arjan van der Gaag
2
+
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8
+ of the Software, and to permit persons to whom the Software is furnished to do
9
+ so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Whenner [![Build Status](https://secure.travis-ci.org/avdgaag/whenner.png?branch=master)](http://travis-ci.org/avdgaag/whenner)
2
+
3
+ ## Introduction
4
+
5
+ A promise represents the eventual result of an asynchronous operation. The
6
+ primary way of interacting with a promise is through its `done` and `fail`
7
+ methods, which registers callbacks to receive either a promise’s eventual value
8
+ or the reason why the promise cannot be fulfilled.
9
+
10
+ ## Installation
11
+
12
+ Whenner is distributed as a Ruby gem, which should be installed on most Macs and
13
+ Linux systems. Once you have ensured you have a working installation of Ruby
14
+ and Ruby gems, install the gem as follows from the command line:
15
+
16
+ $ gem install whenner
17
+
18
+ ## Usage
19
+
20
+ Whenner provides two basic methods to use deferreds:
21
+
22
+ * `Whenner.defer` to create a new deferred object and return its promise. In the
23
+ block to the method you can fulfill or reject the deferred.
24
+ * `Whenner.when` to convert one or more arguments into promises, combining them
25
+ into a single new promise that you can attach callbacks to.
26
+
27
+ Deferred objects can give you a promise that, at some point in the future, will
28
+ resolve to either a fulfilled or rejected state. When that happens, appropriate
29
+ callbacks are called. You can attach such callbacks on a deferred or promise
30
+ using three methods:
31
+
32
+ * `done` to register blocks to be called when the promise is fulfilled;
33
+ * `fail` to register blocks to be called when the promise is rejected;
34
+ * `always` to register blocks to be called when the promise is resolved (either
35
+ fulfilled or rejected);
36
+
37
+ Here's an example of making three asynchronous HTTP requests, waiting for them
38
+ all to finish and acting on their results:
39
+
40
+ ```ruby
41
+ $:.unshift File.expand_path('../lib', __FILE__)
42
+ require 'whenner'
43
+ require 'uri'
44
+ require 'net/http'
45
+
46
+ include Whenner
47
+
48
+ def async_get(uri)
49
+ defer do |f|
50
+ thread = Thread.new do
51
+ response = Net::HTTP.get_response(URI(uri))
52
+ if response.code =~ /^2/
53
+ f.fulfill response.body
54
+ else
55
+ f.reject response.message
56
+ end
57
+ end
58
+ at_exit { thread.join }
59
+ end
60
+ end
61
+
62
+ cnn = async_get('http://edition.cnn.com')
63
+ nytimes = async_get('http://www.nytimes.com')
64
+ google = async_get('http://www.google.nl')
65
+
66
+ Whenner.when(cnn, google, nytimes).done do |results|
67
+ results.map { |str| str[/<title>(.+)<\/title>/, 1] }
68
+ end.done do |titles|
69
+ puts "Success: #{titles.inspect}"
70
+ end
71
+ ```
72
+
73
+ As methods in Ruby can only take a single block, Whenner does not support a
74
+ `then` method yet, that would combine the `done` and `fail` methods. This might
75
+ be implementing in the future using something like this:
76
+
77
+ ```ruby
78
+ defer { async_get('http://google.com') }.then do |on|
79
+ on.done { puts 'Success!' }
80
+ on.fail { puts 'Success!' }
81
+ end
82
+ ```
83
+
84
+ ### Documentation
85
+
86
+ See the inline [API
87
+ docs](http://rubydoc.info/github/avdgaag/whenner/master/frames) for more
88
+ information.
89
+
90
+ ## Other
91
+
92
+ ### Note on Patches/Pull Requests
93
+
94
+ 1. Fork the project.
95
+ 2. Make your feature addition or bug fix.
96
+ 3. Add tests for it. This is important so I don't break it in a future version
97
+ unintentionally.
98
+ 4. Commit, do not mess with rakefile, version, or history. (if you want to have
99
+ your own version, that is fine but bump version in a commit by itself I can
100
+ ignore when I pull)
101
+ 5. Send me a pull request. Bonus points for topic branches.
102
+
103
+ ### Issues
104
+
105
+ Please report any issues, defects or suggestions in the [Github issue
106
+ tracker](https://github.com/avdgaag/whenner/issues).
107
+
108
+ ### What has changed?
109
+
110
+ See the [HISTORY](https://github.com/avdgaag/whenner/blob/master/HISTORY.md) file
111
+ for a detailed changelog.
112
+
113
+ ### Credits
114
+
115
+ Created by: Arjan van der Gaag
116
+ URL: [http://arjanvandergaag.nl](http://arjanvandergaag.nl)
117
+ Project homepage: [http://avdgaag.github.com/whenner](http://avdgaag.github.com/whenner)
118
+ Date: april 2012
119
+ License: [MIT-license](https://github.com/avdgaag/whenner/blob/master/LICENSE) (same as Ruby)
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+ Bundler.setup
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
8
+
9
+ require 'rspec/core/rake_task'
10
+ desc 'Run specs'
11
+ RSpec::Core::RakeTask.new
12
+
13
+ require 'yard'
14
+ desc 'Generate API docs'
15
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,55 @@
1
+ module Whenner
2
+ # A Callback is used internally by {Deferred} to store its callbacks.
3
+ # It provides the same `call` interface as regular blocks, but this
4
+ # will always return a promise for the block's return value.
5
+ #
6
+ # When the block in question returns a regular object, a new deferred for
7
+ # that object is created and immediately fulfilled. When the block raises an
8
+ # exception, the returned promise is rejected with that exception. When the
9
+ # block returns a promise itself, the returned deferred will mimic that
10
+ # promise -- as if that promise is what actually was returned.
11
+ class Callback
12
+ # A callable object, usually a Ruby block.
13
+ #
14
+ # @return [#call]
15
+ attr_reader :block
16
+
17
+ # @return [Deferred] the deferred object representing the block's return
18
+ # value.
19
+ attr_reader :deferred
20
+
21
+ def initialize(block)
22
+ @block = block
23
+ @deferred = Deferred.new
24
+ end
25
+
26
+ # Run the block, passing it any given arguments, and return a promise
27
+ # for its return value.
28
+ #
29
+ # @return [Promise]
30
+ def call(*args)
31
+ update_deferred(*args)
32
+ deferred.promise
33
+ end
34
+
35
+ # @return [Promise] for this callback's {#deferred}.
36
+ # @see #deferred
37
+ def promise
38
+ deferred.promise
39
+ end
40
+
41
+ private
42
+
43
+ def update_deferred(*args)
44
+ retval = block.call(*args)
45
+ if retval.kind_of?(Promise)
46
+ retval.done { |arg| deferred.fulfill(arg) }
47
+ retval.fail { |arg| deferred.reject(arg) }
48
+ else
49
+ deferred.fulfill(retval)
50
+ end
51
+ rescue => e
52
+ deferred.reject(e)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ module Whenner
2
+ module Conversions
3
+ module_function
4
+
5
+ # Convert any object to a promise. When the object in question responds to
6
+ # `to_promise`, the result of that method will be returned. If not, a new
7
+ # deferred object is created and immediately fulfilled with the given
8
+ # object.
9
+ #
10
+ # @param [Object] obj
11
+ # @return [Promise]
12
+ def Promise(obj)
13
+ return obj.to_promise if obj.respond_to?(:to_promise)
14
+ Deferred.new.fulfill(obj)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,173 @@
1
+ module Whenner
2
+ # A deferred object is an operation that will eventually resolve to a result
3
+ # value. A deferred can be in three possible states:
4
+ #
5
+ # * Pending: it has been created but not yet resolved.
6
+ # * Fulfilled: it has been successfully resolved.
7
+ # * Rejected: it has been unsuccessfully resolved.
8
+ #
9
+ # A deferred might transition from pending to fulfilled or rejected, but it
10
+ # will not transition again once resolved (resolved can be either fulfilled
11
+ # or rejected).
12
+ #
13
+ # When a deferred does resolve, it will trigger any applicable callbacks. You
14
+ # can stack on callbacks on a deferred object before it has been resolved and
15
+ # they will be called later. When you register callbacks on an already
16
+ # resolved deferred, the callback will be called immediately. Note that a
17
+ # callback will only be run once.
18
+ #
19
+ # When a callback is in the fulfilled state, it has a value that represents
20
+ # its eventual outcome. When it is rejected, it has a reason.
21
+ class Deferred
22
+ # @return [Promise] a promise for this deferred
23
+ attr_reader :promise
24
+
25
+ def initialize
26
+ @promise = Promise.new(self)
27
+ @state = :pending
28
+ @fulfilled_callbacks = []
29
+ @rejected_callbacks = []
30
+ @always_callbacks = []
31
+ end
32
+
33
+ # The value the deferred was resolved with.
34
+ #
35
+ # @raise [UnresolvedError] when the deferred is still pending
36
+ def value
37
+ raise UnresolvedError unless resolved?
38
+ @value
39
+ end
40
+
41
+ # The reason the deferred was rejected.
42
+ #
43
+ # @raise [UnresolvedError] when the deferred is still pending
44
+ def reason
45
+ raise UnresolvedError unless resolved?
46
+ @reason
47
+ end
48
+
49
+ # @return [Boolean] whether the deferred has not been resolved yet.
50
+ def pending?
51
+ state == :pending
52
+ end
53
+
54
+ # @return [Boolean] whether the deferred was successfully resolved.
55
+ def fulfilled?
56
+ state == :fulfilled
57
+ end
58
+
59
+ # @return [Boolean] whether the deferred was rejected.
60
+ def rejected?
61
+ state == :rejected
62
+ end
63
+
64
+ # @return [Boolean] whether the deferred was either fulfilled or rejected.
65
+ def resolved?
66
+ fulfilled? || rejected?
67
+ end
68
+
69
+ # Fulfill this promise with an optional value. The value will be stored in
70
+ # the deferred and passed along to any registered `done` callbacks.
71
+ #
72
+ # When fulfilling a deferred twice, nothing happens.
73
+ #
74
+ # @raise [CannotTransitionError] when it was already fulfilled.
75
+ # @return [Deferred] self
76
+ def fulfill(value = nil)
77
+ raise CannotTransitionError if rejected?
78
+ return if fulfilled?
79
+ unless resolved?
80
+ self.value = value
81
+ resolve_to(:fulfilled)
82
+ end
83
+ self
84
+ end
85
+
86
+ # Reject this promise with an optional reason. The reason will be stored in
87
+ # the deferred and passed along to any registered `fail` callbacks.
88
+ #
89
+ # When rejecting a deferred twice, nothing happens.
90
+ #
91
+ # @raise [CannotTransitionError] when it was already fulfilled.
92
+ # @return [Deferred] self
93
+ def reject(reason = nil)
94
+ raise CannotTransitionError if fulfilled?
95
+ return if rejected?
96
+ unless resolved?
97
+ self.reason = reason
98
+ resolve_to(:rejected)
99
+ end
100
+ self
101
+ end
102
+
103
+ # @return [Promise]
104
+ def to_promise
105
+ promise
106
+ end
107
+
108
+ # Register a callback to be run when the deferred is fulfilled.
109
+ #
110
+ # @yieldparam [Object] value
111
+ # @return [Promise] a new promise representing the return value
112
+ # of the callback, or -- when that return value is a promise itself
113
+ # -- a promise mimicking that promise.
114
+ def done(&block)
115
+ cb = Callback.new(block)
116
+ fulfilled_callbacks << cb
117
+ cb.call(*callback_response) if fulfilled?
118
+ cb.promise
119
+ end
120
+
121
+ # Register a callback to be run when the deferred is rejected.
122
+ #
123
+ # @yieldparam [Object] reason
124
+ # @return [Promise] a new promise representing the return value
125
+ # of the callback, or -- when that return value is a promise itself
126
+ # -- a promise mimicking that promise.
127
+ def fail(&block)
128
+ cb = Callback.new(block)
129
+ rejected_callbacks << cb
130
+ cb.call(*callback_response) if rejected?
131
+ cb.promise
132
+ end
133
+
134
+ # Register a callback to be run when the deferred is resolved.
135
+ #
136
+ # @yieldparam [Object] value
137
+ # @yieldparam [Object] reason
138
+ # @return [Promise] a new promise representing the return value
139
+ # of the callback, or -- when that return value is a promise itself
140
+ # -- a promise mimicking that promise.
141
+ def always(&block)
142
+ cb = Callback.new(block)
143
+ always_callbacks << cb
144
+ cb.call(*callback_response) if resolved?
145
+ cb.promise
146
+ end
147
+
148
+ private
149
+
150
+ attr_accessor :state
151
+ attr_writer :value, :reason
152
+ attr_reader :fulfilled_callbacks, :rejected_callbacks, :always_callbacks
153
+
154
+ def resolve_to(state)
155
+ self.state = state
156
+ flush
157
+ end
158
+
159
+ def result_callbacks
160
+ fulfilled? ? fulfilled_callbacks : rejected_callbacks
161
+ end
162
+
163
+ def callback_response
164
+ fulfilled? ? value : reason
165
+ end
166
+
167
+ def flush
168
+ (result_callbacks + always_callbacks).each do |cb|
169
+ cb.call(callback_response)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,44 @@
1
+ module Whenner
2
+ # A promise represents the public face of a {Deferred} object. You can use it
3
+ # to add more callbacks to the deferred or inspect its state -- but you
4
+ # cannot resolve it.
5
+ #
6
+ # The methods and attributes of the promise are basically forwarded to the
7
+ # deferred.
8
+ class Promise
9
+ extend Forwardable
10
+
11
+ # @!attribute [r] fulfilled?
12
+ # @return [Boolean] whether the deferred was successfully resolved.
13
+ # @!attribute [r] resolved?
14
+ # @return [Boolean] whether the deferred was either fulfilled or rejected.
15
+ # @!attribute [r] rejected?
16
+ # @return [Boolean] whether the deferred was rejected.
17
+ # @!attribute [r] pending?
18
+ # @return [Boolean] whether the deferred has not been resolved yet.
19
+ # @!attribute [r] reason
20
+ # @return [Object] the reason for the deferred to be rejected.
21
+ # @!attribute [r] value
22
+ # @return [Object] the value that the deferred was fulfilled with.
23
+ # @!method fail(&block)
24
+ # Register a callback to fire when the deferred is rejected.
25
+ # @return [Promise] a new promise for the return value of the block.
26
+ # @!method done(&block)
27
+ # Register a callback to fire when the deferred is fulfilled.
28
+ # @return [Promise] a new promise for the return value of the block.
29
+ # @!method always(&block)
30
+ # Register a callback to fire when the deferred is resolved.
31
+ # @return [Promise] a new promise for the return value of the block.
32
+ def_delegators :@deferred, *%i[
33
+ reason value pending? fulfilled? resolved? rejected? fail done always
34
+ ]
35
+
36
+ def initialize(deferred)
37
+ @deferred = deferred
38
+ end
39
+
40
+ def to_promise
41
+ self
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Whenner
2
+ VERSION = '0.1.1'
3
+ end
data/lib/whenner.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'whenner/version'
2
+ require 'forwardable'
3
+
4
+ require 'whenner/conversions'
5
+ require 'whenner/callback'
6
+ require 'whenner/deferred'
7
+ require 'whenner/promise'
8
+
9
+ module Whenner
10
+ # Generic root exception for the Whenner library. Any other custom
11
+ # exceptions inherit from WhennerError.
12
+ class WhennerError < StandardError; end
13
+
14
+ # Custom exception raised when trying to access a deferred's value or
15
+ # reason before it is resolved.
16
+ #
17
+ # @see WhennerError
18
+ class UnresolvedError < WhennerError; end
19
+
20
+ # Custom exception raised when trying to transition an already resolved
21
+ # deferred.
22
+ #
23
+ # @see WhennerError
24
+ class CannotTransitionError < WhennerError; end
25
+
26
+ module_function
27
+
28
+ # Create a new deferred, resolve it in the block and get its promise back.
29
+ #
30
+ # @yieldparam [Deferred] deferred
31
+ # @return [Promise]
32
+ def defer
33
+ deferred = Deferred.new
34
+ yield deferred
35
+ deferred.promise
36
+ end
37
+
38
+ # Create a new deferred based that will resolve if/when the given promises
39
+ # are resolved. Use to combine multiple promises into a single deferred
40
+ # object.
41
+ #
42
+ # When all the given promises are fulfilled, the resulting promise from
43
+ # `when` if fulfilled with an array of all the values. When one of the given
44
+ # promises is rejected, the resulting promise is rejected with that reason.
45
+ #
46
+ # @param [Object] promises
47
+ # @return [Promise]
48
+ def when(*promises)
49
+ defer do |d|
50
+ promises.each_with_object([]) do |promise, values|
51
+ Conversions.Promise(promise).tap do |p|
52
+ p.done do |value|
53
+ values << value
54
+ d.fulfill(values) if values.size == promises.size
55
+ end
56
+ p.fail do |reason|
57
+ d.reject(reason)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ module Whenner
2
+ describe '#Promise' do
3
+ it 'returns a promise itself' do
4
+ promise = Whenner::Deferred.new.promise
5
+ expect(Whenner::Conversions.Promise(promise)).to be(promise)
6
+ end
7
+
8
+ it 'returns a promise for a deferred' do
9
+ deferred = Whenner::Deferred.new
10
+ expect(Whenner::Conversions.Promise(deferred)).to be(deferred.promise)
11
+ end
12
+
13
+ it 'returns a resolved promise for an object' do
14
+ promise = Whenner::Conversions.Promise('foo')
15
+ expect(promise.value).to eql('foo')
16
+ expect(promise).to be_fulfilled
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,141 @@
1
+ module Whenner
2
+ describe Deferred do
3
+ it 'is pending by default' do
4
+ expect(subject).to be_pending
5
+ end
6
+
7
+ context 'when pending' do
8
+ it 'can be fulfilled' do
9
+ subject.fulfill
10
+ expect(subject).to be_fulfilled
11
+ end
12
+
13
+ it 'can be rejected' do
14
+ subject.reject
15
+ expect(subject).to be_rejected
16
+ end
17
+
18
+ it 'has no value' do
19
+ expect { subject.value }.to raise_error
20
+ end
21
+ end
22
+
23
+ context 'when fulfilled' do
24
+ subject do
25
+ described_class.new.tap { |d| d.fulfill }
26
+ end
27
+
28
+ it 'cannot transition to other states' do
29
+ expect { subject.reject }.to raise_error
30
+ end
31
+
32
+ it 'has a value' do
33
+ expect(subject.value).to be_nil
34
+ end
35
+ end
36
+
37
+ context 'when rejected' do
38
+ subject do
39
+ described_class.new.tap { |d| d.reject(:foo) }
40
+ end
41
+
42
+ it 'cannot transition to other states' do
43
+ expect { subject.fulfill }.to raise_error
44
+ end
45
+
46
+ it 'has a reason' do
47
+ expect(subject.reason).to eql(:foo)
48
+ end
49
+ end
50
+
51
+ it 'creates a promise' do
52
+ expect(subject.promise).to be_kind_of(Promise)
53
+ end
54
+
55
+ context 'callbacks' do
56
+ it 'calls fulfillment callbacks when fulfilled' do
57
+ resolved = false
58
+ subject.done { resolved = true }
59
+ expect { subject.fulfill(:a) }.to change { resolved }.to(true)
60
+ end
61
+
62
+ it 'calls rejection callbacks when rejected' do
63
+ resolved = false
64
+ subject.fail { resolved = true }
65
+ expect { subject.reject(:a) }.to change { resolved }.to(true)
66
+ end
67
+
68
+ it 'calls always callbacks when resolved' do
69
+ resolved = false
70
+ subject.always { resolved = true }
71
+ expect { subject.fulfill(:a) }.to change { resolved }.to(true)
72
+ end
73
+
74
+ it 'passes fulfillment callbacks the value' do
75
+ resolved = nil
76
+ subject.done { |arg| resolved = arg }
77
+ expect { subject.fulfill(:a) }.to change { resolved }.to(:a)
78
+ end
79
+
80
+ it 'passes rejection callbacks the reason' do
81
+ resolved = nil
82
+ subject.done { |arg| resolved = arg }
83
+ expect { subject.fulfill(:a) }.to change { resolved }.to(:a)
84
+ end
85
+
86
+ it 'passes the value and reason to always callbacks' do
87
+ subject.always do |value, reason|
88
+ expect(value).to eql(:a)
89
+ expect(reason).to be_nil
90
+ end
91
+ subject.fulfill(:a)
92
+ end
93
+
94
+ it 'calls callbacks only once' do
95
+ called = 0
96
+ subject.done { called += 1 }
97
+ subject.fulfill(:a)
98
+ subject.fulfill(:b)
99
+ expect(called).to eql(1)
100
+ end
101
+
102
+ it 'runs callbacks in order' do
103
+ output = ''
104
+ subject.done { output << 'a' }
105
+ subject.done { output << 'b' }
106
+ subject.fulfill
107
+ expect(output).to eql('ab')
108
+ end
109
+
110
+ it 'returns a new promise that fulfills to the value' do
111
+ new_promise = subject.done { 'foo' }
112
+ subject.fulfill
113
+ expect(new_promise.value).to eql('foo')
114
+ expect(new_promise).to be_fulfilled
115
+ end
116
+
117
+ it 'returns a new promise that rejects to the reason' do
118
+ new_promise = subject.fail { 'foo' }
119
+ subject.reject
120
+ expect(new_promise.value).to eql('foo')
121
+ expect(new_promise).to be_fulfilled
122
+ end
123
+
124
+ it 'returns a new promise that mimics a promise value' do
125
+ d = Deferred.new
126
+ new_promise = subject.done { d.promise }
127
+ subject.fulfill(:b)
128
+ d.fulfill(:a)
129
+ expect(new_promise.value).to eql(:a)
130
+ end
131
+
132
+ it 'is rejected on exception' do
133
+ called = false
134
+ promise = subject.done { raise 'arg' }
135
+ promise.fail { |e| called = e }
136
+ subject.fulfill
137
+ expect(called).to be_a(RuntimeError)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,51 @@
1
+ module Whenner
2
+ describe Promise do
3
+ let(:deferred) { Deferred.new }
4
+ subject(:promise) { deferred.promise }
5
+
6
+ it 'converts into a promise as itself' do
7
+ expect(promise.to_promise).to be(promise)
8
+ end
9
+
10
+ describe 'callbacks' do
11
+ it 'can add done callbacks to the deferred' do
12
+ expect { promise.done { :a } }.to change { deferred.send(:fulfilled_callbacks).size }.by(1)
13
+ end
14
+
15
+ it 'can add fail callbacks to the deferred' do
16
+ expect { promise.fail { :a } }.to change { deferred.send(:rejected_callbacks).size }.by(1)
17
+ end
18
+
19
+ it 'can add always callbacks to the deferred' do
20
+ expect { promise.always { :a } }.to change { deferred.send(:always_callbacks).size }.by(1)
21
+ end
22
+ end
23
+
24
+ context 'when the deferred is fulfilled' do
25
+ let(:deferred) { Deferred.new.fulfill(:a) }
26
+
27
+ it 'has a value' do
28
+ expect(promise.value).to eql(:a)
29
+ end
30
+
31
+ it 'knows its state' do
32
+ expect(promise).to be_resolved
33
+ expect(promise).to be_fulfilled
34
+ end
35
+ end
36
+
37
+
38
+ context 'when the deferred is rejected' do
39
+ let(:deferred) { Deferred.new.reject(:a) }
40
+
41
+ it 'has a reason' do
42
+ expect(promise.reason).to eql(:a)
43
+ end
44
+
45
+ it 'knows its state' do
46
+ expect(promise).to be_resolved
47
+ expect(promise).to be_rejected
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ describe Whenner do
2
+ describe '#defer' do
3
+ it 'returns a new promise' do
4
+ promise = Whenner.defer { 'bla' }
5
+ expect(promise).to be_kind_of(Whenner::Promise)
6
+ end
7
+
8
+ it 'yields a deferred' do
9
+ expect { |b| Whenner.defer(&b) }.to yield_with_args(Whenner::Deferred)
10
+ end
11
+ end
12
+
13
+ describe '#when' do
14
+ let(:deferred1) { Whenner::Deferred.new }
15
+ let(:deferred2) { Whenner::Deferred.new }
16
+ let!(:promise) { Whenner.when(deferred1.promise, deferred2.promise) }
17
+
18
+ it 'returns a promise' do
19
+ expect(Whenner.when).to be_kind_of(Whenner::Promise)
20
+ end
21
+
22
+ it 'resolves when all given promises are resolved' do
23
+ expect { deferred1.fulfill }.not_to change { promise.resolved? }.from(false)
24
+ expect { deferred2.fulfill }.to change { promise.resolved? }.to(true)
25
+ end
26
+
27
+ it 'fulfills with all values' do
28
+ deferred1.fulfill :a
29
+ deferred2.fulfill :b
30
+ expect(promise.value).to eql([:a, :b])
31
+ end
32
+
33
+ it 'rejects with the first reason' do
34
+ deferred1.reject :a
35
+ deferred2.reject :b
36
+ expect(promise.reason).to eql(:a)
37
+ end
38
+ end
39
+ end
data/whenner.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/whenner/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ # Metadata
6
+ s.name = 'whenner'
7
+ s.version = Whenner::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Arjan van der Gaag']
10
+ s.email = %q{arjan@arjanvandergaag.nl}
11
+ s.description = %q{A simple promises implementation in Ruby.}
12
+ s.homepage = %q{http://avdgaag.github.com/whenner}
13
+ s.summary = <<-EOS
14
+ A promise represents the eventual result of an asynchronous operation. The
15
+ primary way of interacting with a promise is through its `done` and `fail`
16
+ methods, which registers callbacks to receive either a promise’s eventual value
17
+ or the reason why the promise cannot be fulfilled.
18
+ EOS
19
+
20
+ # Files
21
+ s.files = `git ls-files`.split("
22
+ ")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("
24
+ ")
25
+ s.executables = `git ls-files -- bin/*`.split("
26
+ ").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+
29
+ # Rdoc
30
+ s.rdoc_options = ['--charset=UTF-8']
31
+ s.extra_rdoc_files = [
32
+ 'LICENSE',
33
+ 'README.md',
34
+ 'HISTORY.md'
35
+ ]
36
+
37
+ # Dependencies
38
+ s.add_development_dependency 'kramdown'
39
+ s.add_development_dependency 'yard'
40
+ s.add_development_dependency 'rspec'
41
+ s.add_development_dependency 'rake'
42
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: whenner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Arjan van der Gaag
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kramdown
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A simple promises implementation in Ruby.
70
+ email: arjan@arjanvandergaag.nl
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - LICENSE
75
+ - README.md
76
+ - HISTORY.md
77
+ files:
78
+ - .gitignore
79
+ - .rspec
80
+ - .travis.yml
81
+ - .yardopts
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ - HISTORY.md
85
+ - LICENSE
86
+ - README.md
87
+ - Rakefile
88
+ - lib/whenner.rb
89
+ - lib/whenner/callback.rb
90
+ - lib/whenner/conversions.rb
91
+ - lib/whenner/deferred.rb
92
+ - lib/whenner/promise.rb
93
+ - lib/whenner/version.rb
94
+ - spec/whenner/conversions_spec.rb
95
+ - spec/whenner/deferred_spec.rb
96
+ - spec/whenner/promise_spec.rb
97
+ - spec/whenner_spec.rb
98
+ - whenner.gemspec
99
+ homepage: http://avdgaag.github.com/whenner
100
+ licenses: []
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.1.11
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: A promise represents the eventual result of an asynchronous operation. The
123
+ primary way of interacting with a promise is through its `done` and `fail` methods,
124
+ which registers callbacks to receive either a promise’s eventual value or the reason
125
+ why the promise cannot be fulfilled.
126
+ test_files:
127
+ - spec/whenner/conversions_spec.rb
128
+ - spec/whenner/deferred_spec.rb
129
+ - spec/whenner/promise_spec.rb
130
+ - spec/whenner_spec.rb
131
+ has_rdoc: