promise.rb 0.4.0 → 0.5.0

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