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.
344 345 346 |
# File 'opal/stdlib/promise/v2.rb', line 344 def initialize(&block) yield self if block_given? end |
Instance Attribute Details
#next ⇒ Object (readonly)
Returns the value of attribute next.
166 167 168 |
# File 'opal/stdlib/promise/v2.rb', line 166 def next @next end |
#prev ⇒ Object (readonly)
Returns the value of attribute prev.
166 167 168 |
# File 'opal/stdlib/promise/v2.rb', line 166 def prev @prev end |
Class Method Details
.all_resolved(*promises) ⇒ Object
122 123 124 125 126 127 |
# File 'opal/stdlib/promise/v2.rb', line 122 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
105 106 107 108 109 110 111 112 113 |
# File 'opal/stdlib/promise/v2.rb', line 105 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
129 130 131 132 133 134 |
# File 'opal/stdlib/promise/v2.rb', line 129 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
162 163 164 165 166 167 168 169 |
# File 'opal/stdlib/promise/v2.rb', line 162 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_set, true) prom.instance_variable_set(:@value, value) end end |
.race(*promises) ⇒ Object
136 137 138 139 140 141 |
# File 'opal/stdlib/promise/v2.rb', line 136 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
152 153 154 155 156 157 158 159 |
# File 'opal/stdlib/promise/v2.rb', line 152 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_set, true) prom.instance_variable_set(:@value, value) end end |
.resolve(value = nil) ⇒ Object
143 144 145 146 147 148 149 150 |
# File 'opal/stdlib/promise/v2.rb', line 143 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_set, true) prom.instance_variable_set(:@value, value) end end |
.value ⇒ Object
163 164 165 166 167 168 169 170 |
# File 'opal/stdlib/promise/v2.rb', line 163 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_set, true) prom.instance_variable_set(:@value, value) end end |
Instance Method Details
#always(&block) ⇒ Object Also known as: ensure, finally
259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'opal/stdlib/promise/v2.rb', line 259 def always(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value_set, true) prom.instance_variable_set(:@value, val) end prom = `self.finally(function() { return blk(self.$value_internal()); })` prom.instance_variable_set(:@prev, self) prom.instance_variable_set(:@type, :always) (@next ||= []) << prom prom end |
#always!(&block) ⇒ Object Also known as: ensure!, finally!
273 274 275 276 |
# File 'opal/stdlib/promise/v2.rb', line 273 def always!(&block) there_can_be_only_one! always(&block) end |
#and(*promises) ⇒ Object
331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'opal/stdlib/promise/v2.rb', line 331 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
326 327 328 329 |
# File 'opal/stdlib/promise/v2.rb', line 326 def error light_nativity_check! @value if rejected? end |
#fail(&block) ⇒ Object Also known as: catch, rescue
240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'opal/stdlib/promise/v2.rb', line 240 def fail(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value_set, true) 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!
254 255 256 257 |
# File 'opal/stdlib/promise/v2.rb', line 254 def fail!(&block) there_can_be_only_one! fail(&block) end |
#gen_tracing_proc(passing, &block) ⇒ Object
193 194 195 196 197 198 199 |
# File 'opal/stdlib/promise/v2.rb', line 193 def gen_tracing_proc(passing, &block) proc do |i| res = passing.call(i) yield(res) res end end |
#inspect ⇒ Object
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'opal/stdlib/promise/v2.rb', line 356 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}" 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
181 182 183 184 |
# File 'opal/stdlib/promise/v2.rb', line 181 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.
170 171 172 |
# File 'opal/stdlib/promise/v2.rb', line 170 def native? @type != :opal end |
#nativity_check! ⇒ Object
Raise an exception when a non-JS-native method is called on a JS-native promise
175 176 177 |
# File 'opal/stdlib/promise/v2.rb', line 175 def nativity_check! raise ArgumentError, 'this promise is native to JavaScript' if native? end |
#realized? ⇒ Boolean
315 316 317 318 |
# File 'opal/stdlib/promise/v2.rb', line 315 def realized? light_nativity_check! !@realized.nil? end |
#reject(value = nil) ⇒ Object Also known as: reject!
211 212 213 214 215 216 217 218 219 |
# File 'opal/stdlib/promise/v2.rb', line 211 def reject(value = nil) nativity_check! raise ArgumentError, 'this promise was already resolved' if @realized @value_set = true @value = value @realized = :reject @reject_proc.call(value) self end |
#rejected? ⇒ Boolean
310 311 312 313 |
# File 'opal/stdlib/promise/v2.rb', line 310 def rejected? light_nativity_check! @realized == :reject end |
#resolve(value = nil) ⇒ Object Also known as: resolve!
201 202 203 204 205 206 207 208 209 |
# File 'opal/stdlib/promise/v2.rb', line 201 def resolve(value = nil) nativity_check! raise ArgumentError, 'this promise was already resolved' if @realized @value_set = true @value = value @realized = :resolve @resolve_proc.call(value) self end |
#resolved? ⇒ Boolean
305 306 307 308 |
# File 'opal/stdlib/promise/v2.rb', line 305 def resolved? light_nativity_check! @realized == :resolve end |
#then(&block) ⇒ Object Also known as: do
221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'opal/stdlib/promise/v2.rb', line 221 def then(&block) prom = nil blk = gen_tracing_proc(block) do |val| prom.instance_variable_set(:@realized, :resolve) prom.instance_variable_set(:@value_set, true) 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!
235 236 237 238 |
# File 'opal/stdlib/promise/v2.rb', line 235 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.
189 190 191 |
# File 'opal/stdlib/promise/v2.rb', line 189 def there_can_be_only_one! raise ArgumentError, 'a promise has already been chained' if @next && @next.any? end |
#to_v1 ⇒ Object
348 349 350 351 352 353 354 |
# File 'opal/stdlib/promise/v2.rb', line 348 def to_v1 v1 = PromiseV1.new self.then { |i| v1.resolve(i) }.rescue { |i| v1.reject(i) } v1 end |
#trace(depth = nil, &block) ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'opal/stdlib/promise/v2.rb', line 278 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
300 301 302 303 |
# File 'opal/stdlib/promise/v2.rb', line 300 def trace!(*args, &block) there_can_be_only_one! trace(*args, &block) end |
#value ⇒ Object
320 321 322 323 324 |
# File 'opal/stdlib/promise/v2.rb', line 320 def value if resolved? value_internal end end |