promise.rb 0.3.0 → 0.4.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/CHANGELOG.md +8 -0
- data/Gemfile +0 -1
- data/Gemfile.devtools +23 -11
- data/README.md +78 -18
- data/{LICENSE → UNLICENSE} +0 -0
- data/config/flay.yml +1 -1
- data/config/rubocop.yml +0 -9
- data/lib/promise.rb +4 -2
- data/lib/promise/progress.rb +4 -7
- data/lib/promise/version.rb +1 -1
- data/promise.rb.gemspec +2 -2
- data/spec/promise_spec.rb +11 -8
- metadata +6 -5
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# promise.rb changelog
|
2
|
+
|
3
|
+
## 0.4.0 (December 13, 2013)
|
4
|
+
|
5
|
+
* Disclaiming my copyright, promise.rb is now in the Public Domain
|
6
|
+
* on_fulfill argument to #then now takes precedence over block
|
7
|
+
* The rejection reason now defaults to RuntimeError, so that it can be re-raised by #sync
|
8
|
+
* `progress` and `on_progress` are now first class citizens
|
data/Gemfile
CHANGED
data/Gemfile.devtools
CHANGED
@@ -4,6 +4,10 @@ group :development do
|
|
4
4
|
gem 'rake', '~> 10.1.0'
|
5
5
|
gem 'rspec', '~> 2.14.1'
|
6
6
|
gem 'yard', '~> 0.8.7'
|
7
|
+
|
8
|
+
platform :rbx do
|
9
|
+
gem 'rubysl-singleton', '~> 2.0.0'
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
group :yard do
|
@@ -11,14 +15,13 @@ group :yard do
|
|
11
15
|
end
|
12
16
|
|
13
17
|
group :guard do
|
14
|
-
gem 'guard', '~>
|
15
|
-
gem 'guard-bundler', '~>
|
16
|
-
gem 'guard-rspec', '~>
|
17
|
-
gem 'guard-rubocop', '~> 0.
|
18
|
-
gem 'guard-mutant', '~> 0.0.1'
|
18
|
+
gem 'guard', '~> 2.2.4'
|
19
|
+
gem 'guard-bundler', '~> 2.0.0'
|
20
|
+
gem 'guard-rspec', '~> 4.0.4'
|
21
|
+
gem 'guard-rubocop', '~> 1.0.0'
|
19
22
|
|
20
23
|
# file system change event handling
|
21
|
-
gem 'listen', '~>
|
24
|
+
gem 'listen', '~> 2.2.0'
|
22
25
|
gem 'rb-fchange', '~> 0.0.6', require: false
|
23
26
|
gem 'rb-fsevent', '~> 0.9.3', require: false
|
24
27
|
gem 'rb-inotify', '~> 0.9.0', require: false
|
@@ -30,18 +33,27 @@ group :guard do
|
|
30
33
|
end
|
31
34
|
|
32
35
|
group :metrics do
|
33
|
-
gem 'coveralls', '~> 0.
|
36
|
+
gem 'coveralls', '~> 0.7.0'
|
34
37
|
gem 'flay', '~> 2.4.0'
|
35
|
-
gem 'flog', '~> 4.
|
38
|
+
gem 'flog', '~> 4.2.0'
|
36
39
|
gem 'reek', '~> 1.3.2'
|
37
|
-
gem 'rubocop', '~> 0.
|
38
|
-
gem 'simplecov', '~> 0.
|
40
|
+
gem 'rubocop', '~> 0.15.0'
|
41
|
+
gem 'simplecov', '~> 0.8.2'
|
39
42
|
gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
|
40
43
|
|
41
44
|
platforms :ruby_19, :ruby_20 do
|
42
|
-
gem 'mutant', git: 'https://github.com/mbj/mutant.git'
|
45
|
+
gem 'mutant', '~> 0.3.0.rc3', git: 'https://github.com/mbj/mutant.git'
|
46
|
+
gem 'unparser', '~> 0.1.5', git: 'https://github.com/mbj/unparser.git'
|
43
47
|
gem 'yard-spellcheck', '~> 0.1.5'
|
44
48
|
end
|
49
|
+
|
50
|
+
platform :rbx do
|
51
|
+
gem 'json', '~> 1.8.1'
|
52
|
+
gem 'racc', '~> 1.4.10'
|
53
|
+
gem 'rubysl-logger', '~> 2.0.0'
|
54
|
+
gem 'rubysl-open-uri', '~> 2.0.0'
|
55
|
+
gem 'rubysl-prettyprint', '~> 2.0.2'
|
56
|
+
end
|
45
57
|
end
|
46
58
|
|
47
59
|
group :benchmarks do
|
data/README.md
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
# promise.rb [](https://travis-ci.org/lgierth/promise.rb) [](https://codeclimate.com/github/lgierth/promise.rb) [](https://coveralls.io/r/lgierth/promise.rb?branch=master)
|
2
2
|
|
3
3
|
Ruby implementation of the [Promises/A+ spec](http://promisesaplus.com/).
|
4
|
-
100% mutation coverage, tested on 1.9, 2.0, Rubinius, and JRuby.
|
4
|
+
100% [mutation coverage](https://github.com/mbj/mutant), tested on 1.9, 2.0, Rubinius, and JRuby.
|
5
|
+
|
6
|
+
Similar projects:
|
7
|
+
|
8
|
+
- [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md), Promises/A(+) implementation, thread based
|
9
|
+
- [promise](https://github.com/bhuga/promising-future), a.k.a. promising-future, classic promises and futures, thread based
|
10
|
+
- [celluloid-promise](https://github.com/cotag/celluloid-promise), inspired by Q, backed by a Celluloid actor
|
11
|
+
- [em-promise](https://github.com/cotag/em-promise), inspired by Q, backed by an EventMachine reactor
|
12
|
+
- [futuristic](https://github.com/seanlilmateus/futuristic), MacRuby bindings for Grand Central Dispatch
|
13
|
+
- [methodmissing/promise](https://github.com/methodmissing/promise), thread based, abandoned
|
14
|
+
|
15
|
+
## Todo
|
16
|
+
|
17
|
+
- test with https://gist.github.com/joeljackson/5722487
|
5
18
|
|
6
19
|
## Installation
|
7
20
|
|
@@ -19,19 +32,11 @@ Or install it yourself as:
|
|
19
32
|
|
20
33
|
## Usage
|
21
34
|
|
22
|
-
|
35
|
+
This guide assumes that you are familiar with the [Promises/A+ spec](http://promisesaplus.com/). It's a quick read, though.
|
23
36
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
class MyPromise < Promise
|
28
|
-
def defer(callback, arg)
|
29
|
-
callback.dispatch(arg)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
```
|
33
|
-
|
34
|
-
The above scheduling mechanism violates the following section of the spec:
|
37
|
+
promise.rb comes with a very primitive way of scheduling callback dispatch. It
|
38
|
+
immediately executes the callback, instead of scheduling it for execution
|
39
|
+
*after* `Promise#fulfill` or `Promise#reject`, as demanded by the spec:
|
35
40
|
|
36
41
|
> onFulfilled or onRejected must not be called until the execution context
|
37
42
|
> stack contains only platform code.
|
@@ -44,8 +49,8 @@ require 'promise'
|
|
44
49
|
require 'eventmachine'
|
45
50
|
|
46
51
|
class MyPromise < Promise
|
47
|
-
def defer
|
48
|
-
EM.next_tick {
|
52
|
+
def defer
|
53
|
+
EM.next_tick { yield }
|
49
54
|
end
|
50
55
|
end
|
51
56
|
```
|
@@ -76,10 +81,65 @@ end
|
|
76
81
|
failing_stuff.then(proc { |value| }, proc { |reason| p reason })
|
77
82
|
```
|
78
83
|
|
79
|
-
|
84
|
+
promise.rb also comes with the utility method `Promise#sync`, which waits for
|
85
|
+
the promise to be fulfilled and returns the value, or for it to be rejected and
|
86
|
+
re-raises the reason. Using `#sync` requires you to implement `#wait`. You could
|
87
|
+
for example cooperatively schedule fibers waiting for different promises:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
require 'promise'
|
91
|
+
require 'eventmachine'
|
92
|
+
|
93
|
+
class MyPromise < Promise
|
94
|
+
def defer
|
95
|
+
EM.next_tick { yield }
|
96
|
+
end
|
97
|
+
|
98
|
+
def wait
|
99
|
+
fiber = Fiber.current
|
100
|
+
resume = proc do |arg|
|
101
|
+
defer { fiber.resume(arg) }
|
102
|
+
end
|
103
|
+
|
104
|
+
self.then(resume, resume)
|
105
|
+
Fiber.yield
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
promise = MyPromise.new
|
110
|
+
Fiber.new { p promise.sync }.resume
|
111
|
+
promise.fulfill
|
112
|
+
```
|
113
|
+
|
114
|
+
Or have the rejection reason re-raised from `#sync`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
promise = MyPromise.new
|
118
|
+
|
119
|
+
Fiber.new do
|
120
|
+
begin
|
121
|
+
promise.sync
|
122
|
+
rescue MyError
|
123
|
+
p $!
|
124
|
+
end
|
125
|
+
end.resume
|
126
|
+
|
127
|
+
promise.reject(MyError.new)
|
128
|
+
```
|
129
|
+
|
130
|
+
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
|
+
|
132
|
+
```ruby
|
133
|
+
promise = MyPromise.new
|
134
|
+
promise.on_progress { |status| p status }
|
135
|
+
promise.progress(:anything)
|
136
|
+
```
|
137
|
+
|
138
|
+
## Unlicense
|
80
139
|
|
81
|
-
|
82
|
-
|
140
|
+
promise.rb is free and unencumbered public domain software. For more
|
141
|
+
information, see [unlicense.org](http://unlicense.org/) or the accompanying
|
142
|
+
UNLICENSE file.
|
83
143
|
|
84
144
|
## Contributing
|
85
145
|
|
data/{LICENSE → UNLICENSE}
RENAMED
File without changes
|
data/config/flay.yml
CHANGED
data/config/rubocop.yml
CHANGED
@@ -24,15 +24,6 @@ CollectionMethods:
|
|
24
24
|
find: 'detect'
|
25
25
|
find_all: 'select'
|
26
26
|
|
27
|
-
# Do not force public/protected/private keyword to be indented at the same
|
28
|
-
# level as the def keyword. My personal preference is to outdent these keywords
|
29
|
-
# because I think when scanning code it makes it easier to identify the
|
30
|
-
# sections of code and visually separate them. When the keyword is at the same
|
31
|
-
# level I think it sort of blends in with the def keywords and makes it harder
|
32
|
-
# to scan the code and see where the sections are.
|
33
|
-
AccessControl:
|
34
|
-
Enabled: false
|
35
|
-
|
36
27
|
# Limit line length
|
37
28
|
LineLength:
|
38
29
|
Max: 79
|
data/lib/promise.rb
CHANGED
@@ -6,6 +6,8 @@ require 'promise/callback'
|
|
6
6
|
require 'promise/progress'
|
7
7
|
|
8
8
|
class Promise
|
9
|
+
include Promise::Progress
|
10
|
+
|
9
11
|
attr_reader :value, :reason
|
10
12
|
|
11
13
|
def initialize
|
@@ -27,7 +29,7 @@ class Promise
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def then(on_fulfill = nil, on_reject = nil, &block)
|
30
|
-
on_fulfill
|
32
|
+
on_fulfill ||= block
|
31
33
|
next_promise = add_callbacks(on_fulfill, on_reject)
|
32
34
|
|
33
35
|
maybe_dispatch(@on_fulfill.last, @on_reject.last)
|
@@ -47,7 +49,7 @@ class Promise
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
def reject(reason =
|
52
|
+
def reject(reason = RuntimeError)
|
51
53
|
dispatch(@on_reject, reason) do
|
52
54
|
@state = :rejected
|
53
55
|
@reason = reason
|
data/lib/promise/progress.rb
CHANGED
@@ -2,18 +2,15 @@
|
|
2
2
|
|
3
3
|
class Promise
|
4
4
|
module Progress
|
5
|
-
def initialize
|
6
|
-
super
|
7
|
-
@on_progress = []
|
8
|
-
end
|
9
|
-
|
10
5
|
def on_progress(&block)
|
11
|
-
@on_progress
|
6
|
+
@on_progress ||= []
|
7
|
+
@on_progress << block if block_given?
|
8
|
+
@on_progress
|
12
9
|
end
|
13
10
|
|
14
11
|
def progress(status)
|
15
12
|
if pending?
|
16
|
-
|
13
|
+
on_progress.each { |block| block.call(status) }
|
17
14
|
end
|
18
15
|
end
|
19
16
|
end
|
data/lib/promise/version.rb
CHANGED
data/promise.rb.gemspec
CHANGED
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.authors = ['Lars Gierth']
|
11
11
|
spec.email = ['lars.gierth@gmail.com']
|
12
12
|
spec.description = %q{Promises/A+ for Ruby}
|
13
|
-
spec.summary = %q{Promises/A+
|
13
|
+
spec.summary = %q{Ruby implementation of the Promises/A+ spec}
|
14
14
|
spec.homepage = 'https://github.com/lgierth/promise'
|
15
|
-
spec.license = '
|
15
|
+
spec.license = 'Public Domain'
|
16
16
|
|
17
17
|
spec.files = `git ls-files`.split($/)
|
18
18
|
spec.test_files = spec.files.grep(%r{^spec/})
|
data/spec/promise_spec.rb
CHANGED
@@ -117,6 +117,14 @@ describe Promise do
|
|
117
117
|
subject.fulfill(value)
|
118
118
|
expect(result).to eq(value)
|
119
119
|
end
|
120
|
+
|
121
|
+
it 'takes precedence over block' do
|
122
|
+
result = nil
|
123
|
+
subject.then(proc { |_| result = :arg }) { |_| result = :block }
|
124
|
+
|
125
|
+
subject.fulfill(value)
|
126
|
+
expect(result).to be(:arg)
|
127
|
+
end
|
120
128
|
end
|
121
129
|
|
122
130
|
describe '3.2.3 on_reject' do
|
@@ -220,7 +228,7 @@ describe Promise do
|
|
220
228
|
end
|
221
229
|
|
222
230
|
it 'rejects promise2 with error raised by on_fulfill' do
|
223
|
-
promise2 = subject.then(proc { |_|
|
231
|
+
promise2 = subject.then(proc { |_| fail(error) })
|
224
232
|
expect { subject.fulfill(value) }.to raise_error(error)
|
225
233
|
|
226
234
|
expect(promise2).to be_rejected
|
@@ -228,7 +236,7 @@ describe Promise do
|
|
228
236
|
end
|
229
237
|
|
230
238
|
it 'rejects promise2 with error raised by on_reject' do
|
231
|
-
promise2 = subject.then(nil, proc { |_|
|
239
|
+
promise2 = subject.then(nil, proc { |_| fail(error) })
|
232
240
|
expect { subject.reject(reason) }.to raise_error(error)
|
233
241
|
|
234
242
|
expect(promise2).to be_rejected
|
@@ -305,11 +313,6 @@ describe Promise do
|
|
305
313
|
end
|
306
314
|
|
307
315
|
describe '#progress' do
|
308
|
-
let(:klass) do
|
309
|
-
Class.new(Promise) { include Promise::Progress }
|
310
|
-
end
|
311
|
-
let(:subject) { klass.new }
|
312
|
-
|
313
316
|
let(:status) { double('status') }
|
314
317
|
|
315
318
|
it 'calls the callbacks in the order of calls to #on_progress' do
|
@@ -355,7 +358,7 @@ describe Promise do
|
|
355
358
|
|
356
359
|
it 'does not require a reason' do
|
357
360
|
subject.reject
|
358
|
-
expect(subject.reason).to be(
|
361
|
+
expect(subject.reason).to be(RuntimeError)
|
359
362
|
end
|
360
363
|
end
|
361
364
|
|
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
|
+
version: 0.4.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
|
+
date: 2013-12-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -37,11 +37,12 @@ files:
|
|
37
37
|
- .gitignore
|
38
38
|
- .rspec
|
39
39
|
- .travis.yml
|
40
|
+
- CHANGELOG.md
|
40
41
|
- Gemfile
|
41
42
|
- Gemfile.devtools
|
42
|
-
- LICENSE
|
43
43
|
- README.md
|
44
44
|
- Rakefile
|
45
|
+
- UNLICENSE
|
45
46
|
- config/devtools.yml
|
46
47
|
- config/flay.yml
|
47
48
|
- config/flog.yml
|
@@ -58,7 +59,7 @@ files:
|
|
58
59
|
- spec/spec_helper.rb
|
59
60
|
homepage: https://github.com/lgierth/promise
|
60
61
|
licenses:
|
61
|
-
-
|
62
|
+
- Public Domain
|
62
63
|
post_install_message:
|
63
64
|
rdoc_options: []
|
64
65
|
require_paths:
|
@@ -80,7 +81,7 @@ rubyforge_project:
|
|
80
81
|
rubygems_version: 1.8.23
|
81
82
|
signing_key:
|
82
83
|
specification_version: 3
|
83
|
-
summary: Promises/A+
|
84
|
+
summary: Ruby implementation of the Promises/A+ spec
|
84
85
|
test_files:
|
85
86
|
- spec/promise_spec.rb
|
86
87
|
- spec/spec_helper.rb
|