promise.rb 0.4.0 → 0.5.0

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.
data/.travis.yml CHANGED
@@ -2,7 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - jruby-19mode
5
+ - jruby
6
6
  - rbx
7
7
  - ruby-head
8
8
  - jruby-head
@@ -10,4 +10,5 @@ matrix:
10
10
  allow_failures:
11
11
  - rvm: ruby-head
12
12
  - rvm: jruby-head
13
+ fast_finish: true
13
14
  script: bundle exec rake -t metrics:coverage ci
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # promise.rb changelog
2
2
 
3
+ ## 0.5.0 (December 16, 2013)
4
+
5
+ * Fulfillment value and rejection reason are no longer being frozen
6
+ * Rejection reason always gets a sensible backtrace now
7
+ * Have pending specs for deviations from the A+ spec
8
+
3
9
  ## 0.4.0 (December 13, 2013)
4
10
 
5
11
  * Disclaiming my copyright, promise.rb is now in the Public Domain
data/README.md CHANGED
@@ -6,15 +6,14 @@ Ruby implementation of the [Promises/A+ spec](http://promisesaplus.com/).
6
6
  Similar projects:
7
7
 
8
8
  - [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md), Promises/A(+) implementation, thread based
9
+ - [ruby-thread](https://github.com/meh/ruby-thread), thread/mutex/condition variable based, thread safe
9
10
  - [promise](https://github.com/bhuga/promising-future), a.k.a. promising-future, classic promises and futures, thread based
10
11
  - [celluloid-promise](https://github.com/cotag/celluloid-promise), inspired by Q, backed by a Celluloid actor
11
12
  - [em-promise](https://github.com/cotag/em-promise), inspired by Q, backed by an EventMachine reactor
12
13
  - [futuristic](https://github.com/seanlilmateus/futuristic), MacRuby bindings for Grand Central Dispatch
13
14
  - [methodmissing/promise](https://github.com/methodmissing/promise), thread based, abandoned
14
15
 
15
- ## Todo
16
-
17
- - test with https://gist.github.com/joeljackson/5722487
16
+ *Note that promise.rb is probably not thread safe.*
18
17
 
19
18
  ## Installation
20
19
 
@@ -81,6 +80,8 @@ end
81
80
  failing_stuff.then(proc { |value| }, proc { |reason| p reason })
82
81
  ```
83
82
 
83
+ ### Waiting for fulfillment/rejection
84
+
84
85
  promise.rb also comes with the utility method `Promise#sync`, which waits for
85
86
  the promise to be fulfilled and returns the value, or for it to be rejected and
86
87
  re-raises the reason. Using `#sync` requires you to implement `#wait`. You could
@@ -127,6 +128,28 @@ end.resume
127
128
  promise.reject(MyError.new)
128
129
  ```
129
130
 
131
+ ### Chaining promises
132
+
133
+ As per the A+ spec, every call to `#then` returns a new promise, which assumes
134
+ the first promise's state. That means it passes its `#fulfill` and `#reject`
135
+ methods to first promise's `#then`, shortcircuiting the two promises. In case
136
+ a callback returns a promise, it'll instead assume that promise's state.
137
+
138
+ Imagine the `#fulfill` and `#reject` calls in the following example happening
139
+ somewhere in a background Fiber or so.
140
+
141
+ ```ruby
142
+ require 'promise'
143
+
144
+ Promise.new
145
+ .tap(&:fulfill)
146
+ .then { Promise.new.tap(&:fulfill) }
147
+ .then { Promise.new.tap(&:reject) }
148
+ .then(nil, proc { |reason| p reason })
149
+ ```
150
+
151
+ ### Progress callbacks
152
+
130
153
  Very simple progress callbacks, as per Promises/A, are supported as well. They have been dropped in A+, but I found them to be a useful mechanism - if kept simple. Callback dispatch happens immediately in the call to `#progress`, in the order of definition via `#on_progress`. Also note that `#on_progress` does not return a new promise for chaining - the progress mechanism is meant to be very lightweight, and ignores many of the constraints and guarantees of `then`.
131
154
 
132
155
  ```ruby
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "bundler/gem_tasks"
3
+ task :default => 'ci:metrics'
4
4
 
5
5
  # Added by devtools
6
6
  require 'devtools'
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 10
3
- total_score: 49
3
+ total_score: 50
data/config/reek.yml CHANGED
@@ -57,7 +57,7 @@ TooManyInstanceVariables:
57
57
  TooManyMethods:
58
58
  enabled: true
59
59
  exclude: []
60
- max_methods: 13
60
+ max_methods: 14
61
61
  TooManyStatements:
62
62
  enabled: true
63
63
  exclude:
data/lib/promise.rb CHANGED
@@ -49,7 +49,8 @@ class Promise
49
49
  end
50
50
  end
51
51
 
52
- def reject(reason = RuntimeError)
52
+ def reject(reason = nil)
53
+ reason = build_reason(reason || RuntimeError, caller)
53
54
  dispatch(@on_reject, reason) do
54
55
  @state = :rejected
55
56
  @reason = reason
@@ -68,7 +69,6 @@ class Promise
68
69
  def dispatch(callbacks, arg)
69
70
  if pending?
70
71
  yield
71
- arg.freeze
72
72
  callbacks.each { |callback| dispatch!(callback, arg) }
73
73
  end
74
74
 
@@ -97,4 +97,10 @@ class Promise
97
97
  def defer
98
98
  yield
99
99
  end
100
+
101
+ def build_reason(reason, backtrace)
102
+ reason = reason.new if reason.respond_to?(:new)
103
+ reason.set_backtrace(backtrace) unless reason.backtrace
104
+ reason
105
+ end
100
106
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Promise
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/spec/promise_spec.rb CHANGED
@@ -7,11 +7,13 @@ describe Promise do
7
7
 
8
8
  let(:value) { double('value') }
9
9
  let(:other_value) { double('other_value') }
10
+
11
+ let(:backtrace) { caller }
10
12
  let(:reason) do
11
- StandardError.new('reason').tap { |ex| ex.set_backtrace(caller) }
13
+ StandardError.new('reason').tap { |ex| ex.set_backtrace(backtrace) }
12
14
  end
13
15
  let(:other_reason) do
14
- StandardError.new('other_reason').tap { |ex| ex.set_backtrace(caller) }
16
+ StandardError.new('other_reason').tap { |ex| ex.set_backtrace(backtrace) }
15
17
  end
16
18
 
17
19
  describe '3.1.1 pending' do
@@ -33,14 +35,16 @@ describe Promise do
33
35
  expect(subject).to be_fulfilled
34
36
  end
35
37
 
36
- it 'has an immutable value' do
38
+ it 'has a value' do
37
39
  subject.fulfill(value)
38
40
  expect(subject.value).to eq(value)
39
41
 
40
42
  subject.fulfill(other_value)
41
43
  expect(subject.value).to eq(value)
44
+ end
42
45
 
43
- expect(subject.value).to be_frozen
46
+ it 'freezes the value' do
47
+ pending 'Dropped in 74da6e9'
44
48
  end
45
49
  end
46
50
 
@@ -51,14 +55,16 @@ describe Promise do
51
55
  expect(subject).to be_rejected
52
56
  end
53
57
 
54
- it 'has an immutable reason' do
58
+ it 'has a reason' do
55
59
  subject.reject(reason)
56
60
  expect(subject.reason).to eq(reason)
57
61
 
58
62
  subject.reject(other_reason)
59
63
  expect(subject.reason).to eq(reason)
64
+ end
60
65
 
61
- expect(subject.reason).to be_frozen
66
+ it 'freezes the reason' do
67
+ pending 'Dropped in 74da6e9'
62
68
  end
63
69
  end
64
70
 
@@ -164,7 +170,7 @@ describe Promise do
164
170
 
165
171
  describe '3.2.4' do
166
172
  it 'returns before on_fulfill or on_reject is called' do
167
- pending
173
+ pending 'To be implemented by application code'
168
174
  end
169
175
  end
170
176
 
@@ -358,7 +364,29 @@ describe Promise do
358
364
 
359
365
  it 'does not require a reason' do
360
366
  subject.reject
361
- expect(subject.reason).to be(RuntimeError)
367
+ expect(subject.reason).to be_a(RuntimeError)
368
+ end
369
+
370
+ it 'allows nil reason' do
371
+ subject.reject(nil)
372
+ expect(subject.reason).to be_a(RuntimeError)
373
+ end
374
+
375
+ it 'sets the backtrace' do
376
+ subject.reject
377
+ expect(subject.reason.backtrace[0])
378
+ .to include(__FILE__ + ':' + (__LINE__ - 2).to_s)
379
+ end
380
+
381
+ it 'does not override backtrace' do
382
+ subject.reject(reason)
383
+ expect(subject.reason.backtrace).to be(backtrace)
384
+ end
385
+
386
+ it 'builds exception object for custom error class' do
387
+ cls = Class.new(StandardError)
388
+ subject.reject(cls)
389
+ expect(subject.reason).to be_a(cls)
362
390
  end
363
391
  end
364
392
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promise.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-12 00:00:00.000000000 Z
12
+ date: 2013-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec