em-promise 1.0.0 → 1.0.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.
- data/MIT-LICENSE +20 -0
- data/README.textile +32 -0
- data/Rakefile +10 -0
- data/lib/em-promise.rb +3 -0
- data/lib/em-promise/defer.rb +181 -0
- data/lib/em-promise/version.rb +5 -0
- data/spec/defer.rb +30 -0
- metadata +12 -4
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
h1. EM-Promise
|
2
|
+
|
3
|
+
A promise/deferred implementation inspired by "AngularJS":http://docs.angularjs.org/api/ng.$q see this documentation for use cases.
|
4
|
+
|
5
|
+
From the perspective of dealing with error handling, deferred and promise apis are to asynchronous programing what try, catch and throw keywords are to synchronous programming.
|
6
|
+
|
7
|
+
<pre><code class="ruby">
|
8
|
+
def asyncGreet(name)
|
9
|
+
deferred = EM::Defer.new
|
10
|
+
|
11
|
+
EM::Timer.new(5) do
|
12
|
+
EM.defer do
|
13
|
+
deferred.resolve("Hello #{name}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
deferred.promise
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
EventMachine.run do
|
22
|
+
|
23
|
+
promise = asyncGreet('Robin Hood')
|
24
|
+
promise.then(proc { |greeting|
|
25
|
+
p "Success: #{greeting}"
|
26
|
+
}, proc { |reason|
|
27
|
+
p "Failed: #{reason}"
|
28
|
+
})
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
</code></pre>
|
data/Rakefile
ADDED
data/lib/em-promise.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
#
|
5
|
+
# Creates a 'Deferred' object which represents a task which will finish in the future.
|
6
|
+
#
|
7
|
+
class Defer
|
8
|
+
|
9
|
+
class Promise
|
10
|
+
end
|
11
|
+
|
12
|
+
class DeferredPromise < Promise
|
13
|
+
def initialize(defer)
|
14
|
+
@defer = defer
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def then(callback = nil, errback = nil, &blk)
|
19
|
+
result = Defer.new
|
20
|
+
|
21
|
+
callback ||= blk
|
22
|
+
|
23
|
+
wrappedCallback = proc { |value|
|
24
|
+
begin
|
25
|
+
result.resolve(callback.nil? ? value : callback.call(value))
|
26
|
+
rescue => e
|
27
|
+
#
|
28
|
+
# TODO:: add debugging output here
|
29
|
+
#
|
30
|
+
result.reject(e);
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
wrappedErrback = proc { |reason|
|
35
|
+
begin
|
36
|
+
result.resolve(errback.nil? ? Defer.reject(reason) : errback.call(reason))
|
37
|
+
rescue => e
|
38
|
+
#
|
39
|
+
# TODO:: add debugging output here
|
40
|
+
#
|
41
|
+
result.reject(e);
|
42
|
+
end
|
43
|
+
}
|
44
|
+
|
45
|
+
#
|
46
|
+
# Schedule as we are touching arrays
|
47
|
+
# => Everything else is locally scoped
|
48
|
+
#
|
49
|
+
EM.schedule do
|
50
|
+
pending_array = pending
|
51
|
+
|
52
|
+
if pending_array.nil?
|
53
|
+
value.then(wrappedCallback, wrappedErrback)
|
54
|
+
else
|
55
|
+
pending_array << [wrappedCallback, wrappedErrback]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
result.promise
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
|
66
|
+
def pending
|
67
|
+
@defer.instance_eval { @pending }
|
68
|
+
end
|
69
|
+
|
70
|
+
def value
|
71
|
+
@defer.instance_eval { @value }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class ResolvedPromise < Promise
|
77
|
+
def initialize(response, error = false)
|
78
|
+
@error = error
|
79
|
+
@response = response
|
80
|
+
end
|
81
|
+
|
82
|
+
def then(callback = nil, errback = nil, &blk)
|
83
|
+
result = Defer.new
|
84
|
+
|
85
|
+
callback ||= blk
|
86
|
+
|
87
|
+
EM.next_tick {
|
88
|
+
if @error
|
89
|
+
result.resolve(errback.nil? ? Defer.reject(@response) : errback.call(@response))
|
90
|
+
else
|
91
|
+
result.resolve(callback.nil? ? @response : callback.call(@response))
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
result.promise
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def initialize
|
101
|
+
@pending = []
|
102
|
+
@value = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def resolve(val = nil)
|
107
|
+
EM.schedule do
|
108
|
+
if !!@pending
|
109
|
+
callbacks = @pending
|
110
|
+
@pending = nil
|
111
|
+
@value = ref(val)
|
112
|
+
|
113
|
+
if callbacks.length > 0
|
114
|
+
EM.next_tick {
|
115
|
+
callbacks.each do |callback|
|
116
|
+
@value.then(callback[0], callback[1])
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def reject(reason = nil)
|
126
|
+
resolve(Defer.reject(reason))
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def promise
|
131
|
+
DeferredPromise.new( self )
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
#
|
136
|
+
# Creates a promise that is resolved as rejected with the specified reason. This api should be
|
137
|
+
# used to forward rejection in a chain of promises. If you are dealing with the last promise in
|
138
|
+
# a promise chain, you don't need to worry about it.
|
139
|
+
#
|
140
|
+
# When comparing deferreds/promises to the familiar behaviour of try/catch/throw, think of
|
141
|
+
# reject as the raise keyword in Ruby. This also means that if you "catch" an error via
|
142
|
+
# a promise error callback and you want to forward the error to the promise derived from the
|
143
|
+
# current promise, you have to "rethrow" the error by returning a rejection constructed via
|
144
|
+
# reject.
|
145
|
+
#
|
146
|
+
# @example handling rejections
|
147
|
+
#
|
148
|
+
# #!/usr/bin/env ruby
|
149
|
+
#
|
150
|
+
# require 'rubygems' # or use Bundler.setup
|
151
|
+
# require 'eventmachine'
|
152
|
+
#
|
153
|
+
# promiseB = promiseA.then(lambda {|result|
|
154
|
+
# # success: do something and resolve promiseB with the old or a new result
|
155
|
+
# return result
|
156
|
+
# }, lambda {|reason|
|
157
|
+
# # error: handle the error if possible and resolve promiseB with newPromiseOrValue,
|
158
|
+
# # otherwise forward the rejection to promiseB
|
159
|
+
# if canHandle(reason)
|
160
|
+
# # handle the error and recover
|
161
|
+
# return newPromiseOrValue
|
162
|
+
# end
|
163
|
+
# return Defer.reject(reason)
|
164
|
+
# })
|
165
|
+
#
|
166
|
+
# @param [Object] reason constant, message, exception or an object representing the rejection reason.
|
167
|
+
def self.reject(reason = nil)
|
168
|
+
return ResolvedPromise.new( reason, true ) # A resolved failed promise
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
protected
|
173
|
+
|
174
|
+
|
175
|
+
def ref(value)
|
176
|
+
return value if value.is_a?(Promise)
|
177
|
+
return ResolvedPromise.new( value ) # A resolved success promise
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
data/spec/defer.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'em-promise'
|
5
|
+
|
6
|
+
|
7
|
+
describe EventMachine::Defer do
|
8
|
+
|
9
|
+
|
10
|
+
it "should fulfill the promise and execute all success callbacks in the registration order" do
|
11
|
+
EventMachine.run {
|
12
|
+
proc { |name|
|
13
|
+
deferred = EM::Defer.new
|
14
|
+
EM.defer { deferred.resolve("Hello #{name}") }
|
15
|
+
deferred.promise.then(proc {|result|
|
16
|
+
result += "?"
|
17
|
+
result
|
18
|
+
})
|
19
|
+
}.call('Robin Hood').then(proc { |greeting|
|
20
|
+
greeting.should == 'Hello Robin Hood?'
|
21
|
+
EventMachine.stop
|
22
|
+
}, proc { |reason|
|
23
|
+
fail(reason)
|
24
|
+
EventMachine.stop
|
25
|
+
})
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-promise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
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: 2012-10-
|
12
|
+
date: 2012-10-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
@@ -49,7 +49,14 @@ email:
|
|
49
49
|
executables: []
|
50
50
|
extensions: []
|
51
51
|
extra_rdoc_files: []
|
52
|
-
files:
|
52
|
+
files:
|
53
|
+
- lib/em-promise/defer.rb
|
54
|
+
- lib/em-promise/version.rb
|
55
|
+
- lib/em-promise.rb
|
56
|
+
- MIT-LICENSE
|
57
|
+
- Rakefile
|
58
|
+
- README.textile
|
59
|
+
- spec/defer.rb
|
53
60
|
homepage: https://github.com/cotag/em-promise
|
54
61
|
licenses: []
|
55
62
|
post_install_message:
|
@@ -74,4 +81,5 @@ rubygems_version: 1.8.24
|
|
74
81
|
signing_key:
|
75
82
|
specification_version: 3
|
76
83
|
summary: EventMachine based, promise/deferred implementation
|
77
|
-
test_files:
|
84
|
+
test_files:
|
85
|
+
- spec/defer.rb
|