Class: PromiseV2
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/v2'
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 = PromiseV2.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 = PromiseV2.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 = PromiseV2.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 PromiseV2 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
PromiseV2.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:
PromiseV2.when(get_json, get_json2).then |first, second|
puts "got two json payloads: #{first}, #{second}"
end
Instance Attribute Summary collapse
-
#next ⇒ Object
readonly
Returns the value of attribute next.
-
#prev ⇒ Object
readonly
Returns the value of attribute prev.
Class Method Summary collapse
- .all_resolved(*promises) ⇒ Object
- .allocate ⇒ Object
- .any(*promises) ⇒ Object
- .error ⇒ Object
- .race(*promises) ⇒ Object
- .reject(value = nil) ⇒ Object
- .resolve(value = nil) ⇒ Object
- .value ⇒ Object
- .when(*promises) ⇒ Object (also: all)
Instance Method Summary collapse
- #always(&block) ⇒ Object (also: #ensure, #finally)
- #always!(&block) ⇒ Object (also: #ensure!, #finally!)
- #and(*promises) ⇒ Object
- #error ⇒ Object
- #fail(&block) ⇒ Object (also: #catch, #rescue)
- #fail!(&block) ⇒ Object (also: #catch!, #rescue!)
- #gen_tracing_proc(passing, &block) ⇒ Object
-
#initialize {|_self| ... } ⇒ PromiseV2
constructor
A new instance of PromiseV2.
- #inspect ⇒ Object
-
#light_nativity_check! ⇒ Object
Raise an exception when a non-JS-native method is called on a JS-native promise but permits some typed promises.
-
#native? ⇒ Boolean
Is this promise native to JavaScript? This means, that methods like resolve or reject won't be available.
-
#nativity_check! ⇒ Object
Raise an exception when a non-JS-native method is called on a JS-native promise.
- #realized? ⇒ Boolean
- #reject(value = nil) ⇒ Object (also: #reject!)
- #rejected? ⇒ Boolean
- #resolve(value = nil) ⇒ Object (also: #resolve!)
- #resolved? ⇒ Boolean
- #then(&block) ⇒ Object (also: #do)
- #then!(&block) ⇒ Object (also: #do!)
-
#there_can_be_only_one! ⇒ Object
Allow only one chain to be present, as needed by the previous implementation.
- #to_v1 ⇒ Object
- #trace(depth = nil, &block) ⇒ Object
- #trace!(*args, &block) ⇒ Object
- #value ⇒ Object
Constructor Details
#initialize {|_self| ... } ⇒ PromiseV2
Returns a new instance of PromiseV2.
339 340 341 |
# File 'opal/stdlib/promise/v2.rb', line 339 def initialize(&block) yield self if block_given? end |
Instance Attribute Details
#next ⇒ Object (readonly)
Returns the value of attribute next.
162 163 164 |
# File 'opal/stdlib/promise/v2.rb', line 162 def next @next end |
#prev ⇒ Object (readonly)
Returns the value of attribute prev.
162 163 164 |
# File 'opal/stdlib/promise/v2.rb', line 162 def prev @prev end |
Class Method Details
.all_resolved(*promises) ⇒ Object
120 121 122 123 124 125 |
# File 'opal/stdlib/promise/v2.rb', line 120 def all_resolved(*promises) promises = Array(promises.length == 1 ? promises.first : promises) `Promise.allResolved(#{promises})`.tap do |prom| prom.instance_variable_set(:@type, :all_resolved) end end |
.allocate ⇒ Object
103 104 105 106 107 108 109 110 111 |
# File 'opal/stdlib/promise/v2.rb', line 103 def allocate ok, fail = nil, nil prom = `new self.$$constructor(function(_ok, _fail) { #{ok} = _ok; #{fail} = _fail; })` prom.instance_variable_set(:@type, :opal) prom.instance_variable_set(:@resolve_proc, ok) prom.instance_variable_set(:@reject_proc, fail) prom end |
.any(*promises) ⇒ Object
127 128 129 130 131 132 |
# File 'opal/stdlib/promise/v2.rb', line 127 def any(*promises) promises = Array(promises.length == 1 ? promises.first : promises) `Promise.any(#{promises})`.tap do |prom| prom.instance_variable_set(:@type, :any) end end |
.error ⇒ Object
158 159 160 161 162 163 164 |
# File 'opal/stdlib/promise/v2.rb', line 158 def reject(value = nil) `Promise.reject(#{value})`.tap do |prom| prom.instance_variable_set(:@type, :reject) prom.instance_variable_set(:@realized, :reject) prom.instance_variable_set(:@value, value) end end |
.race(*promises) ⇒ Object
134 135 136 137 138 139 |
# File 'opal/stdlib/promise/v2.rb', line 134 def race(*promises) promises = Array(promises.length == 1 ? promises.first : promises) `Promise.race(#{promises})`.tap do |prom| prom.instance_variable_set(:@type, :race) end end |
.reject(value = nil) ⇒ Object
149 150 151 152 153 154 155 |
# File 'opal/stdlib/promise/v2.rb', line 149 def reject(value = nil) `Promise.reject(#{value})`.tap do |prom| prom.instance_variable_set(:@type, :reject) prom.instance_variable_set(:@realized, :reject) prom.instance_variable_set(:@value, value) end end |
.resolve(value = nil) ⇒ Object
141 142 143 144 145 146 147 |
# File 'opal/stdlib/promise/v2.rb', line 141 def resolve(value = nil) `Promise.resolve(#{value})`.tap do |prom| prom.instance_variable_set(:@type, :resolve) prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value, value) end end |
.value ⇒ Object
159 160 161 162 163 164 165 |
# File 'opal/stdlib/promise/v2.rb', line 159 def resolve(value = nil) `Promise.resolve(#{value})`.tap do |prom| prom.instance_variable_set(:@type, :resolve) prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value, value) end end |
Instance Method Details
#always(&block) ⇒ Object Also known as: ensure, finally
251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'opal/stdlib/promise/v2.rb', line 251 def always(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value, val) end prom = `self.finally(#{blk})` prom.instance_variable_set(:@prev, self) prom.instance_variable_set(:@type, :always) (@next ||= []) << prom prom end |
#always!(&block) ⇒ Object Also known as: ensure!, finally!
264 265 266 267 |
# File 'opal/stdlib/promise/v2.rb', line 264 def always!(&block) there_can_be_only_one! always(&block) end |
#and(*promises) ⇒ Object
326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'opal/stdlib/promise/v2.rb', line 326 def and(*promises) promises = promises.map do |i| if PromiseV2 === i i else PromiseV2.value(i) end end PromiseV2.when(self, *promises).then do |a, *b| [*a, *b] end end |
#error ⇒ Object
321 322 323 324 |
# File 'opal/stdlib/promise/v2.rb', line 321 def error light_nativity_check! @value if rejected? end |
#fail(&block) ⇒ Object Also known as: catch, rescue
233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'opal/stdlib/promise/v2.rb', line 233 def fail(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value, val) end prom = `self.catch(#{blk})` prom.instance_variable_set(:@prev, self) prom.instance_variable_set(:@type, :fail) (@next ||= []) << prom prom end |
#fail!(&block) ⇒ Object Also known as: catch!, rescue!
246 247 248 249 |
# File 'opal/stdlib/promise/v2.rb', line 246 def fail!(&block) there_can_be_only_one! fail(&block) end |
#gen_tracing_proc(passing, &block) ⇒ Object
189 190 191 192 193 194 195 |
# File 'opal/stdlib/promise/v2.rb', line 189 def gen_tracing_proc(passing, &block) proc do |i| res = passing.call(i) yield(res) res end end |
#inspect ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'opal/stdlib/promise/v2.rb', line 351 def inspect result = "#<#{self.class}" if @type result += ":#{@type}" unless %i[opal resolve reject].include? @type else result += ':native' end result += ":#{@realized}" if @realized result += "(#{object_id})" if @next && @next.any? result += " >> #{@next.inspect}" end result += ": #{@value.inspect}" if @value result += '>' result end |
#light_nativity_check! ⇒ Object
Raise an exception when a non-JS-native method is called on a JS-native promise but permits some typed promises
177 178 179 180 |
# File 'opal/stdlib/promise/v2.rb', line 177 def light_nativity_check! return if %i[reject resolve trace always fail then].include? @type raise ArgumentError, 'this promise is native to JavaScript' if native? end |
#native? ⇒ Boolean
Is this promise native to JavaScript? This means, that methods like resolve or reject won't be available.
166 167 168 |
# File 'opal/stdlib/promise/v2.rb', line 166 def native? @type != :opal end |
#nativity_check! ⇒ Object
Raise an exception when a non-JS-native method is called on a JS-native promise
171 172 173 |
# File 'opal/stdlib/promise/v2.rb', line 171 def nativity_check! raise ArgumentError, 'this promise is native to JavaScript' if native? end |
#realized? ⇒ Boolean
306 307 308 309 |
# File 'opal/stdlib/promise/v2.rb', line 306 def realized? light_nativity_check! !@realized.nil? end |
#reject(value = nil) ⇒ Object Also known as: reject!
206 207 208 209 210 211 212 213 |
# File 'opal/stdlib/promise/v2.rb', line 206 def reject(value = nil) nativity_check! raise ArgumentError, 'this promise was already resolved' if @realized @value = value @realized = :reject @reject_proc.call(value) self end |
#rejected? ⇒ Boolean
301 302 303 304 |
# File 'opal/stdlib/promise/v2.rb', line 301 def rejected? light_nativity_check! @realized == :reject end |
#resolve(value = nil) ⇒ Object Also known as: resolve!
197 198 199 200 201 202 203 204 |
# File 'opal/stdlib/promise/v2.rb', line 197 def resolve(value = nil) nativity_check! raise ArgumentError, 'this promise was already resolved' if @realized @value = value @realized = :resolve @resolve_proc.call(value) self end |
#resolved? ⇒ Boolean
296 297 298 299 |
# File 'opal/stdlib/promise/v2.rb', line 296 def resolved? light_nativity_check! @realized == :resolve end |
#then(&block) ⇒ Object Also known as: do
215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'opal/stdlib/promise/v2.rb', line 215 def then(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value, val) end prom = `self.then(#{blk})` prom.instance_variable_set(:@prev, self) prom.instance_variable_set(:@type, :then) (@next ||= []) << prom prom end |
#then!(&block) ⇒ Object Also known as: do!
228 229 230 231 |
# File 'opal/stdlib/promise/v2.rb', line 228 def then!(&block) there_can_be_only_one! self.then(&block) end |
#there_can_be_only_one! ⇒ Object
Allow only one chain to be present, as needed by the previous implementation. This isn't a strict check - it's always possible on the JS side to chain a given block.
185 186 187 |
# File 'opal/stdlib/promise/v2.rb', line 185 def there_can_be_only_one! raise ArgumentError, 'a promise has already been chained' if @next && @next.any? end |
#to_v1 ⇒ Object
343 344 345 346 347 348 349 |
# File 'opal/stdlib/promise/v2.rb', line 343 def to_v1 v1 = PromiseV1.new self.then { |i| v1.resolve(i) }.rescue { |i| v1.reject(i) } v1 end |
#trace(depth = nil, &block) ⇒ Object
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'opal/stdlib/promise/v2.rb', line 269 def trace(depth = nil, &block) prom = self.then do values = [] prom = self while prom && (!depth || depth > 0) val = nil begin val = prom.value rescue ArgumentError val = :native end values.unshift(val) depth -= 1 if depth prom = prom.prev end yield(*values) end prom.instance_variable_set(:@type, :trace) prom end |
#trace!(*args, &block) ⇒ Object
291 292 293 294 |
# File 'opal/stdlib/promise/v2.rb', line 291 def trace!(*args, &block) there_can_be_only_one! trace(*args, &block) end |