Class: Fear::Future

Inherits:
Object
  • Object
show all
Includes:
Awaitable
Defined in:
lib/fear/future.rb

Overview

Asynchronous computations that yield futures are created with the Fear.future call:

success = "Hello"
f = Fear.future { success + ' future!' }
f.on_success do |result|
  puts result
end

Multiple callbacks may be registered; there is no guarantee that they will be executed in a particular order.

The future may contain an exception and this means that the future failed. Futures obtained through combinators have the same error as the future they were obtained from.

f = Fear.future { 5 }
g = Fear.future { 3 }
f.flat_map do |x|
  g.map { |y| x + y }
end

The same program may be written using Fear.for

Fear.for(Fear.future { 5 }, Fear.future { 3 }) do |x, y|
  x + y
end

Futures use Concurrent::Promise under the hood. Fear.future accepts optional configuration Hash passed directly to underlying promise. For example, run it on custom thread pool.

require 'open-uri'

future = Fear.future(executor: :io) { open('https://example.com/') }

future.map(executor: :fast, &:read).each do |body|
  puts "#{body}"
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(promise = nil, **options) { ... } ⇒ Future

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Future

Yields:

  • given block and evaluate it in the future.

See Also:

  • Fear.future

62
63
64
65
66
67
68
69
70
71
# File 'lib/fear/future.rb', line 62

def initialize(promise = nil, **options, &block)
  if block_given? && promise
    raise ArgumentError, "pass block or future"
  end

  @options = options
  @promise = promise || Concurrent::Promise.execute(@options) do
    Fear.try(&block)
  end
end

Class Method Details

.failed(exception) ⇒ Fear::Future

Creates an already completed Future with the specified error.


474
475
476
477
478
# File 'lib/fear/future.rb', line 474

def failed(exception)
  new(executor: Concurrent::ImmediateExecutor.new) do
    raise exception
  end
end

.successful(result) ⇒ Fear::Future

Creates an already completed Future with the specified result.


484
485
486
487
488
# File 'lib/fear/future.rb', line 484

def successful(result)
  new(executor: Concurrent::ImmediateExecutor.new) do
    result
  end
end

Instance Method Details

#__ready__(at_most) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


461
462
463
464
465
466
467
# File 'lib/fear/future.rb', line 461

def __ready__(at_most)
  if promise.wait(at_most).complete?
    self
  else
    raise Timeout::Error
  end
end

#__result__(at_most) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


456
457
458
# File 'lib/fear/future.rb', line 456

def __result__(at_most)
  __ready__(at_most).value.get_or_else { raise "promise not completed" }
end

#and_thenObject

Note:

that if one of the chained and_then callbacks throws

Applies the side-effecting block to the result of self future, and returns a new future with the result of this future.

This method allows one to enforce that the callbacks are executed in a specified order.

an error, that error is not propagated to the subsequent and_then callbacks. Instead, the subsequent and_then callbacks are given the original value of this future.

Examples:

The following example prints out 5:

f = Fear.future { 5 }
f.and_then do
  m.success { }fail| 'runtime error' }
end.and_then do |m|
  m.success { |value| puts value } # it evaluates this branch
  m.failure { |error| puts error.massage }
end

443
444
445
446
447
448
449
450
451
452
453
# File 'lib/fear/future.rb', line 443

def and_then
  promise = Promise.new(@options)
  on_complete do |try|
    Fear.try do
      Fear::Try.matcher { |m| yield(m) }.call_or_else(try, &:itself)
    end
    promise.complete!(try)
  end

  promise.to_future
end

#completed?true, false

Returns whether the future has already been completed with a value or an error.

Examples:

future = Fear.future { }
future.completed? #=> false
sleep(1)
future.completed? #=> true

207
208
209
# File 'lib/fear/future.rb', line 207

def completed?
  promise.fulfilled?
end

#fallback_to(fallback) ⇒ Fear::Future

Creates a new future which holds the result of self future if it was completed successfully, or, if not, the result of the fallback future if fallback is completed successfully. If both futures are failed, the resulting future holds the error object of the first future.

Examples:

f = Fear.future { fail 'error' }
g = Fear.future { 5 }
f.fallback_to(g) # evaluates to 5

408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/fear/future.rb', line 408

def fallback_to(fallback)
  promise = Promise.new(@options)
  on_complete_match do |m|
    m.success { |value| promise.complete!(value) }
    m.failure do |error|
      fallback.on_complete_match do |m2|
        m2.success { |value| promise.complete!(value) }
        m2.failure { promise.failure!(error) }
      end
    end
  end

  promise.to_future
end

#flat_map {|| ... } ⇒ Fear::Future

Creates a new future by applying a block to the successful result of this future, and returns the result of the function as the new future. If this future is completed with an exception then the new future will also contain this exception.

Examples:

f1 = Fear.future { 5 }
f2 = Fear.future { 3 }
f1.flat_map do |v1|
  f1.map do |v2|
    v2 * v1
  end
end

Yield Parameters:

  • (any)

296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/fear/future.rb', line 296

def flat_map
  promise = Promise.new(@options)
  on_complete_match do |m|
    m.case(Fear::Failure) { |failure| promise.complete!(failure) }
    m.success do |value|
      begin
        yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
      rescue StandardError => error
        promise.failure!(error)
      end
    end
  end
  promise.to_future
end

#map(&block) ⇒ Fear::Future

Creates a new future by applying a block to the successful result of this future. If this future is completed with an error then the new future will also contain this error.

Examples:

future = Fear.future { 2 }
future.map { |v| v * 2 } #=> the same as Fear.future { 2 * 2 }

270
271
272
273
274
275
276
277
# File 'lib/fear/future.rb', line 270

def map(&block)
  promise = Promise.new(@options)
  on_complete do |try|
    promise.complete!(try.map(&block))
  end

  promise.to_future
end

#on_complete {|| ... } ⇒ self

When this future is completed call the provided block.

If the future has already been completed, this will either be applied immediately or be scheduled asynchronously.

Examples:

Fear.future { }.on_complete do |try|
  try.map(&:do_the_job)
end

Yield Parameters:


168
169
170
171
172
173
# File 'lib/fear/future.rb', line 168

def on_complete
  promise.add_observer do |_time, try, _error|
    yield try
  end
  self
end

#on_complete_match {|| ... } ⇒ self

When this future is completed match against result.

If the future has already been completed, this will either be applied immediately or be scheduled asynchronously.

Examples:

Fear.future { }.on_complete_match do |m|
  m.success { |result| }
  m.failure { |error| }
end

Yield Parameters:


188
189
190
191
192
193
# File 'lib/fear/future.rb', line 188

def on_complete_match
  promise.add_observer do |_time, try, _error|
    Fear::Try.matcher { |m| yield(m) }.call_or_else(try, &:itself)
  end
  self
end

#on_failure {|| ... } ⇒ self

When this future is completed with a failure apply the provided callback to the error.

If the future has already been completed with a failure, this will either be applied immediately or be scheduled asynchronously.

Will not be called in case that the future is completed with a value.

Examples:

Fear.future { }.on_failure do |error|
  if error.is_a?(HTTPError)
    # ...
  end
end

Yield Parameters:

  • (StandardError)

128
129
130
131
132
133
134
# File 'lib/fear/future.rb', line 128

def on_failure
  on_complete do |result|
    if result.failure?
      yield result.exception
    end
  end
end

#on_failure_match {|m| ... } ⇒ self

When this future is completed with a failure match against the error.

If the future has already been completed with a failure, this will either be applied immediately or be scheduled asynchronously.

Will not be called in case that the future is completed with a value.

Examples:

Fear.future { }.on_failure_match do |m|
  m.case(HTTPError) { |error| ... }
end

Yield Parameters:


150
151
152
153
154
# File 'lib/fear/future.rb', line 150

def on_failure_match
  on_failure do |error|
    Fear.matcher { |m| yield(m) }.call_or_else(error, &:itself)
  end
end

#on_success {|value| ... } ⇒ self Also known as: each

Calls the provided callback when this future is completed successfully.

If the future has already been completed with a value, this will either be applied immediately or be scheduled asynchronously.

Examples:

Fear.future { }.on_success do |value|
  # ...
end

Yield Parameters:

  • value (any)

See Also:


88
89
90
91
92
# File 'lib/fear/future.rb', line 88

def on_success(&block)
  on_complete do |result|
    result.each(&block)
  end
end

#on_success_match {|m| ... } ⇒ self

When this future is completed successfully match against its result

If the future has already been completed with a value, this will either be applied immediately or be scheduled asynchronously.

Examples:

Fear.future { }.on_success_match do |m|
  m.case(42) { ... }
end

Yield Parameters:


106
107
108
109
110
# File 'lib/fear/future.rb', line 106

def on_success_match
  on_success do |value|
    Fear.matcher { |m| yield(m) }.call_or_else(value, &:itself)
  end
end

#recover(&block) ⇒ Fear::Future

Creates a new future that will handle any matching error that this future might contain. If there is no match, or if this future contains a valid result then the new future will contain the same.

Examples:

Fear.future { 6 / 0 }.recover { |error| 0  } # result: 0
Fear.future { 6 / 0 }.recover do |m|
  m.case(ZeroDivisionError) { 0 }
  m.case(OtherTypeOfError) { |error| ... }
end # result: 0

352
353
354
355
356
357
358
359
# File 'lib/fear/future.rb', line 352

def recover(&block)
  promise = Promise.new(@options)
  on_complete do |try|
    promise.complete!(try.recover(&block))
  end

  promise.to_future
end

#select {|| ... } ⇒ Fear::Future

Creates a new future by filtering the value of the current future with a predicate.

If the current future contains a value which satisfies the predicate, the new future will also hold that value. Otherwise, the resulting future will fail with a NoSuchElementError.

If the current future fails, then the resulting future also fails.

Examples:

f = Fear.future { 5 }
f.select { |value| value % 2 == 1 } # evaluates to 5
f.select { |value| value % 2 == 0 } # fail with NoSuchElementError

Yield Parameters:

  • (#get)

328
329
330
331
332
333
334
335
336
# File 'lib/fear/future.rb', line 328

def select
  map do |result|
    if yield(result)
      result
    else
      raise NoSuchElementError, "#select predicate is not satisfied"
    end
  end
end

#transform(success, failure) {|success, failure| ... } ⇒ Fear::Future

Creates a new future by applying the success function to the successful result of this future, or the failure function to the failed result. If there is any non-fatal error raised when success or failure is applied, that error will be propagated to the resulting future.

Examples:

Fear.future { open('http://example.com').read }
  .transform(
     ->(value) { ... },
     ->(error) { ... },
  )

Yield Parameters:

  • success (#get)

    function that transforms a successful result of the receiver into a successful result of the returned future

  • failure (#exception)

    function that transforms a failure of the receiver into a failure of the returned future


251
252
253
254
255
256
257
258
# File 'lib/fear/future.rb', line 251

def transform(success, failure)
  promise = Promise.new(@options)
  on_complete_match do |m|
    m.success { |value| promise.success(success.(value)) }
    m.failure { |error| promise.failure(failure.(error)) }
  end
  promise.to_future
end

#valueFear::Option<Fear::Try>

The value of this Future.


219
220
221
# File 'lib/fear/future.rb', line 219

def value
  Fear.option(promise.value(0))
end

#zip(other) ⇒ Fear::Future

Zips the values of self and other future, and creates a new future holding the array of their results.

If self future fails, the resulting future is failed with the error stored in self. Otherwise, if other future fails, the resulting future is failed with the error stored in other.

Examples:

future1 = Fear.future { call_service1 }
future1 = Fear.future { call_service2 }
future1.zip(future2) #=> returns the same result as Fear.future { [call_service1, call_service2] },
  # but it performs two calls asynchronously

378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/fear/future.rb', line 378

def zip(other)
  promise = Promise.new(@options)
  on_complete_match do |m|
    m.success do |value|
      other.on_complete do |other_try|
        promise.complete!(other_try.map { |other_value| [value, other_value] })
      end
    end
    m.failure do |error|
      promise.failure!(error)
    end
  end

  promise.to_future
end