Class: PromiseV2

Inherits:
Object show all
Defined in:
opal/stdlib/promise/v2.rb

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ PromiseV2

Returns a new instance of PromiseV2.

Yields:

  • (_self)

Yield Parameters:

  • _self (PromiseV2)

    the object that the method was called on



344
345
346
# File 'opal/stdlib/promise/v2.rb', line 344

def initialize(&block)
  yield self if block_given?
end

Instance Attribute Details

#nextObject (readonly)

Returns the value of attribute next.



166
167
168
# File 'opal/stdlib/promise/v2.rb', line 166

def next
  @next
end

#prevObject (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

.allocateObject



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

.errorObject



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

.valueObject



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

.when(*promises) ⇒ Object Also known as: all



115
116
117
118
119
120
# File 'opal/stdlib/promise/v2.rb', line 115

def when(*promises)
  promises = Array(promises.length == 1 ? promises.first : promises)
  `Promise.all(#{promises})`.tap do |prom|
    prom.instance_variable_set(:@type, :when)
  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

#errorObject



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

#inspectObject



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

Raises:

  • (ArgumentError)


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.

Returns:



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

Raises:

  • (ArgumentError)


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

Returns:



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!

Raises:

  • (ArgumentError)


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

Returns:



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!

Raises:

  • (ArgumentError)


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

Returns:



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.

Raises:

  • (ArgumentError)


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_v1Object



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

#valueObject



320
321
322
323
324
# File 'opal/stdlib/promise/v2.rb', line 320

def value
  if resolved?
    value_internal
  end
end