Class: Promise
Overview
Promise is used to help structure asynchronous code.
It is available in the Opal standard library, and can be required in any Opal application:
require 'promise'
Basic Usage
Promises are created and returned as objects with the assumption that they will eventually be resolved or rejected, but never both. A Promise has a #then and #fail method (or one of their aliases) that can be used to register a block that gets called once resolved or rejected.
promise = Promise.new
promise.then {
puts "resolved!"
}.fail {
puts "rejected!"
}
# some time later
promise.resolve
# => "resolved!"
It is important to remember that a promise can only be resolved or rejected once, so the block will only ever be called once (or not at all).
Resolving Promises
To resolve a promise, means to inform the Promise that it has succeeded or evaluated to a useful value. #resolve can be passed a value which is then passed into the block handler:
def get_json
promise = Promise.new
HTTP.get("some_url") do |req|
promise.resolve req.json
end
promise
end
get_json.then do |json|
puts "got some JSON from server"
end
Rejecting Promises
Promises are also designed to handle error cases, or situations where an outcome is not as expected. Taking the previous example, we can also pass a value to a #reject call, which passes that object to the registered #fail handler:
def get_json
promise = Promise.new
HTTP.get("some_url") do |req|
if req.ok?
promise.resolve req.json
else
promise.reject req
end
promise
end
get_json.then {
# ...
}.fail { |req|
puts "it went wrong: #{req.message}"
}
Chaining Promises
Promises become even more useful when chained together. Each #then or #fail call returns a new Promise which can be used to chain more and more handlers together.
promise.then { wait_for_something }.then { do_something_else }
Rejections are propagated through the entire chain, so a "catch all" handler can be attached at the end of the tail:
promise.then { ... }.then { ... }.fail { ... }
Composing Promises
Promise.when can be used to wait for more than one promise to resolve (or reject). Using the previous example, we could request two different json requests and wait for both to finish:
Promise.when(get_json, get_json2).then |first, second|
puts "got two json payloads: #{first}, #{second}"
end
Defined Under Namespace
Instance Attribute Summary collapse
-
#error ⇒ Object
readonly
Returns the value of attribute error.
-
#next ⇒ Object
readonly
Returns the value of attribute next.
-
#prev ⇒ Object
readonly
Returns the value of attribute prev.
Class Method Summary collapse
Instance Method Summary collapse
- #<<(promise) ⇒ Object
- #>>(promise) ⇒ Object
- #^(promise) ⇒ Object
- #act? ⇒ Boolean
- #action ⇒ Object
- #always(&block) ⇒ Object (also: #finally, #ensure)
- #always!(&block) ⇒ Object (also: #finally!, #ensure!)
- #exception!(error) ⇒ Object
- #exception? ⇒ Boolean
- #fail(&block) ⇒ Object (also: #rescue, #catch)
- #fail!(&block) ⇒ Object (also: #rescue!, #catch!)
-
#initialize(action = {}) ⇒ Promise
constructor
A new instance of Promise.
- #inspect ⇒ Object
- #realized? ⇒ Boolean
- #reject(value = nil) ⇒ Object
- #reject!(value) ⇒ Object
- #rejected? ⇒ Boolean
- #resolve(value = nil) ⇒ Object
- #resolve!(value) ⇒ Object
- #resolved? ⇒ Boolean
- #then(&block) ⇒ Object (also: #do)
- #then!(&block) ⇒ Object (also: #do!)
- #there_can_be_only_one! ⇒ Object
- #trace(depth = nil, &block) ⇒ Object
- #trace!(*args, &block) ⇒ Object
- #value ⇒ Object
Constructor Details
#initialize(action = {}) ⇒ Promise
Returns a new instance of Promise
115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'opal/stdlib/promise.rb', line 115 def initialize(action = {}) @action = action @realized = false @exception = false @value = nil @error = nil @delayed = false @prev = nil @next = [] end |
Instance Attribute Details
#error ⇒ Object (readonly)
Returns the value of attribute error
113 114 115 |
# File 'opal/stdlib/promise.rb', line 113 def error @error end |
#next ⇒ Object (readonly)
Returns the value of attribute next
113 114 115 |
# File 'opal/stdlib/promise.rb', line 113 def next @next end |
#prev ⇒ Object (readonly)
Returns the value of attribute prev
113 114 115 |
# File 'opal/stdlib/promise.rb', line 113 def prev @prev end |
Class Method Details
.error(value) ⇒ Object
105 106 107 |
# File 'opal/stdlib/promise.rb', line 105 def self.error(value) new.reject(value) end |
.value(value) ⇒ Object
101 102 103 |
# File 'opal/stdlib/promise.rb', line 101 def self.value(value) new.resolve(value) end |
.when(*promises) ⇒ Object
109 110 111 |
# File 'opal/stdlib/promise.rb', line 109 def self.when(*promises) When.new(promises) end |
Instance Method Details
#<<(promise) ⇒ Object
167 168 169 170 171 |
# File 'opal/stdlib/promise.rb', line 167 def <<(promise) @prev = promise self end |
#>>(promise) ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'opal/stdlib/promise.rb', line 173 def >>(promise) @next << promise if exception? promise.reject(@delayed[0]) elsif resolved? promise.resolve(@delayed ? @delayed[0] : value) elsif rejected? if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error) promise.reject(@delayed ? @delayed[0] : error) elsif promise.action.include?(:always) promise.reject(@delayed ? @delayed[0] : error) end end self end |
#^(promise) ⇒ Object
160 161 162 163 164 165 |
# File 'opal/stdlib/promise.rb', line 160 def ^(promise) promise << self self >> promise promise end |
#act? ⇒ Boolean
136 137 138 |
# File 'opal/stdlib/promise.rb', line 136 def act? @action.has_key?(:success) || @action.has_key?(:always) end |
#action ⇒ Object
140 141 142 |
# File 'opal/stdlib/promise.rb', line 140 def action @action.keys end |
#always(&block) ⇒ Object Also known as: finally, ensure
293 294 295 |
# File 'opal/stdlib/promise.rb', line 293 def always(&block) self ^ Promise.new(always: block) end |
#always!(&block) ⇒ Object Also known as: finally!, ensure!
297 298 299 300 |
# File 'opal/stdlib/promise.rb', line 297 def always!(&block) there_can_be_only_one! always(&block) end |
#exception!(error) ⇒ Object
261 262 263 264 265 |
# File 'opal/stdlib/promise.rb', line 261 def exception!(error) @exception = true reject!(error) end |
#exception? ⇒ Boolean
144 145 146 |
# File 'opal/stdlib/promise.rb', line 144 def exception? @exception end |
#fail(&block) ⇒ Object Also known as: rescue, catch
279 280 281 |
# File 'opal/stdlib/promise.rb', line 279 def fail(&block) self ^ Promise.new(failure: block) end |
#fail!(&block) ⇒ Object Also known as: rescue!, catch!
283 284 285 286 |
# File 'opal/stdlib/promise.rb', line 283 def fail!(&block) there_can_be_only_one! fail(&block) end |
#inspect ⇒ Object
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'opal/stdlib/promise.rb', line 322 def inspect result = "#<#{self.class}(#{object_id})" if @next.any? result += " >> #{@next.inspect}" end if realized? result += ": #{(@value || @error).inspect}>" else result += ">" end result end |
#realized? ⇒ Boolean
148 149 150 |
# File 'opal/stdlib/promise.rb', line 148 def realized? !!@realized end |
#reject(value = nil) ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'opal/stdlib/promise.rb', line 224 def reject(value = nil) if realized? raise ArgumentError, 'the promise has already been realized' end if Promise === value return (value << @prev) ^ self end begin if block = @action[:failure] || @action[:always] value = block.call(value) end if @action.has_key?(:always) resolve!(value) else reject!(value) end rescue Exception => e exception!(e) end self end |
#reject!(value) ⇒ Object
250 251 252 253 254 255 256 257 258 259 |
# File 'opal/stdlib/promise.rb', line 250 def reject!(value) @realized = :reject @error = value if @next.any? @next.each { |p| p.reject(value) } else @delayed = [value] end end |
#rejected? ⇒ Boolean
156 157 158 |
# File 'opal/stdlib/promise.rb', line 156 def rejected? @realized == :reject end |
#resolve(value = nil) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'opal/stdlib/promise.rb', line 191 def resolve(value = nil) if realized? raise ArgumentError, 'the promise has already been realized' end if Promise === value return (value << @prev) ^ self end begin if block = @action[:success] || @action[:always] value = block.call(value) end resolve!(value) rescue Exception => e exception!(e) end self end |
#resolve!(value) ⇒ Object
213 214 215 216 217 218 219 220 221 222 |
# File 'opal/stdlib/promise.rb', line 213 def resolve!(value) @realized = :resolve @value = value if @next.any? @next.each { |p| p.resolve(value) } else @delayed = [value] end end |
#resolved? ⇒ Boolean
152 153 154 |
# File 'opal/stdlib/promise.rb', line 152 def resolved? @realized == :resolve end |
#then(&block) ⇒ Object Also known as: do
267 268 269 |
# File 'opal/stdlib/promise.rb', line 267 def then(&block) self ^ Promise.new(success: block) end |
#then!(&block) ⇒ Object Also known as: do!
271 272 273 274 |
# File 'opal/stdlib/promise.rb', line 271 def then!(&block) there_can_be_only_one! self.then(&block) end |
#there_can_be_only_one! ⇒ Object
316 317 318 319 320 |
# File 'opal/stdlib/promise.rb', line 316 def there_can_be_only_one! if @next.any? raise ArgumentError, 'a promise has already been chained' end end |
#trace(depth = nil, &block) ⇒ Object
307 308 309 |
# File 'opal/stdlib/promise.rb', line 307 def trace(depth = nil, &block) self ^ Trace.new(depth, block) end |
#trace!(*args, &block) ⇒ Object
311 312 313 314 |
# File 'opal/stdlib/promise.rb', line 311 def trace!(*args, &block) there_can_be_only_one! trace(*args, &block) end |