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.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.key?(:success) || @action.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
295 296 297 |
# File 'opal/stdlib/promise.rb', line 295 def always(&block) self ^ Promise.new(always: block) end |
#always!(&block) ⇒ Object Also known as: finally!, ensure!
299 300 301 302 |
# File 'opal/stdlib/promise.rb', line 299 def always!(&block) there_can_be_only_one! always(&block) end |
#exception!(error) ⇒ Object
263 264 265 266 267 |
# File 'opal/stdlib/promise.rb', line 263 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
281 282 283 |
# File 'opal/stdlib/promise.rb', line 281 def fail(&block) self ^ Promise.new(failure: block) end |
#fail!(&block) ⇒ Object Also known as: rescue!, catch!
285 286 287 288 |
# File 'opal/stdlib/promise.rb', line 285 def fail!(&block) there_can_be_only_one! fail(&block) end |
#inspect ⇒ Object
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'opal/stdlib/promise.rb', line 324 def inspect result = "#<#{self.class}(#{object_id})" if @next.any? result += " >> #{@next.inspect}" end result += if realized? ": #{(@value || @error).inspect}>" else '>' end result end |
#realized? ⇒ Boolean
148 149 150 |
# File 'opal/stdlib/promise.rb', line 148 def realized? @realized != false end |
#reject(value = nil) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'opal/stdlib/promise.rb', line 225 def reject(value = nil) if realized? raise ArgumentError, 'the promise has already been realized' end if Promise === value return (value << @prev) ^ self end begin block = @action[:failure] || @action[:always] if block value = block.call(value) end if @action.key?(:always) resolve!(value) else reject!(value) end rescue Exception => e exception!(e) end self end |
#reject!(value) ⇒ Object
252 253 254 255 256 257 258 259 260 261 |
# File 'opal/stdlib/promise.rb', line 252 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 212 |
# 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 block = @action[:success] || @action[:always] if block value = block.call(value) end resolve!(value) rescue Exception => e exception!(e) end self end |
#resolve!(value) ⇒ Object
214 215 216 217 218 219 220 221 222 223 |
# File 'opal/stdlib/promise.rb', line 214 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
269 270 271 |
# File 'opal/stdlib/promise.rb', line 269 def then(&block) self ^ Promise.new(success: block) end |
#then!(&block) ⇒ Object Also known as: do!
273 274 275 276 |
# File 'opal/stdlib/promise.rb', line 273 def then!(&block) there_can_be_only_one! self.then(&block) end |
#there_can_be_only_one! ⇒ Object
318 319 320 321 322 |
# File 'opal/stdlib/promise.rb', line 318 def there_can_be_only_one! if @next.any? raise ArgumentError, 'a promise has already been chained' end end |
#trace(depth = nil, &block) ⇒ Object
309 310 311 |
# File 'opal/stdlib/promise.rb', line 309 def trace(depth = nil, &block) self ^ Trace.new(depth, block) end |
#trace!(*args, &block) ⇒ Object
313 314 315 316 |
# File 'opal/stdlib/promise.rb', line 313 def trace!(*args, &block) there_can_be_only_one! trace(*args, &block) end |