whenner 0.1.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.
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: