class Fear::Future
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
@see github.com/scala/scala/blob/2.11.x/src/library/scala/concurrent/Future.scala
Attributes
Public Class Methods
Creates an already completed Future
with the specified error. @param exception [StandardError] @return [Fear::Future]
# File lib/fear/future.rb, line 480 def failed(exception) new(executor: Concurrent::ImmediateExecutor.new) do raise exception end end
@param promise [nil, Concurrent::Future] converts
+Concurrent::Promise+ into +Fear::Future+.
@param options [see Concurrent::Future] options will be passed
directly to +Concurrent::Promise+
@yield given block and evaluate it in the future. @api private @see Fear.future
# 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
Creates an already completed Future
with the specified result. @param result [Object] @return [Fear::Future]
# File lib/fear/future.rb, line 490 def successful(result) new(executor: Concurrent::ImmediateExecutor.new) do result end end
Public Instance Methods
@api private
# File lib/fear/future.rb, line 467 def __ready__(at_most) if promise.wait(at_most).complete? self else raise Timeout::Error end end
@api private
# File lib/fear/future.rb, line 462 def __result__(at_most) __ready__(at_most).value.get_or_else { raise "promise not completed" } end
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.
@note that if one of the chained and_then
callbacks throws 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.
@example 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
# File lib/fear/future.rb, line 449 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
Returns whether the future has already been completed with a value or an error.
@return [true, false] true
if the future is already
completed, +false+ otherwise.
@example
future = Fear.future { } future.completed? #=> false sleep(1) future.completed? #=> true
# File lib/fear/future.rb, line 207 def completed? promise.fulfilled? end
Asynchronously processes the value in the future once the value becomes available.
Will not be called if the future fails. @yieldparam [any] yields with successful feature value @see {#on_complete}
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.
@param fallback [Fear::Future] @return [Fear::Future]
@example
f = Fear.future { fail 'error' } g = Fear.future { 5 } f.fallback_to(g) # evaluates to 5
# File lib/fear/future.rb, line 414 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
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.
@yieldparam [any] @return [Fear::Future]
@example
f1 = Fear.future { 5 } f2 = Fear.future { 3 } f1.flat_map do |v1| f1.map do |v2| v2 * v1 end end
# 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| yield(value).on_complete { |callback_result| promise.complete!(callback_result) } rescue StandardError => error promise.failure!(error) end end promise.to_future end
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.
@return [Fear::Future]
@example
future = Fear.future { 2 } future.map { |v| v * 2 } #=> the same as Fear.future { 2 * 2 }
# 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
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. @yieldparam [Fear::Try] @return [self]
@example
Fear.future { }.on_complete do |try| try.map(&:do_the_job) end
# File lib/fear/future.rb, line 168 def on_complete promise.add_observer do |_time, try, _error| yield try end self end
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. @yieldparam [Fear::TryPatternMatch] @return [self]
@example
Fear.future { }.on_complete_match do |m| m.success { |result| } m.failure { |error| } end
# 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
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. @yieldparam [StandardError] @return [self]
@example
Fear.future { }.on_failure do |error| if error.is_a?(HTTPError) # ... end end
# File lib/fear/future.rb, line 128 def on_failure on_complete do |result| if result.failure? yield result.exception end end end
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. @yieldparam [Fear::PatternMatch] m @return [self]
@example
Fear.future { }.on_failure_match do |m| m.case(HTTPError) { |error| ... } end
# 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
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. @yieldparam [any] value @return [self] @see transform
@example
Fear.future { }.on_success do |value| # ... end
# File lib/fear/future.rb, line 88 def on_success(&block) on_complete do |result| result.each(&block) end end
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. @yieldparam [Fear::PatternMatch] m @return [self]
@example
Fear.future { }.on_success_match do |m| m.case(42) { ... } end
# 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
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.
@return [Fear::Future]
@example
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
# File lib/fear/future.rb, line 350 def recover(&block) promise = Promise.new(@options) on_complete do |try| promise.complete!(try.recover(&block)) end promise.to_future end
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.
@yieldparam [#get] @return [Fear::Future]
@example
f = Fear.future { 5 } f.select { |value| value % 2 == 1 } # evaluates to 5 f.select { |value| value % 2 == 0 } # fail with NoSuchElementError
# File lib/fear/future.rb, line 326 def select map do |result| if yield(result) result else raise NoSuchElementError, "#select predicate is not satisfied" end end end
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.
@yieldparam success [#get] function that transforms a successful result of the
receiver into a successful result of the returned future
@yieldparam failure [#exception] function that transforms a failure of the
receiver into a failure of the returned future
@return [Fear::Future] a future that will be completed with the
transformed value
@example
Fear.future { open('http://example.com').read } .transform( ->(value) { ... }, ->(error) { ... }, )
# 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
The value of this Future
.
@return [Fear::Option<Fear::Try>] if the future is not completed
the returned value will be +Fear::None+. If the future is completed the value will be +Fear::Some<Fear::Success>+ if it contains a valid result, or +Fear::Some<Fear::Failure>+ if it contains an error.
# File lib/fear/future.rb, line 219 def value Fear.option(promise.value(0)) end
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
.
@param other [Fear::Future] @return [Fear::Future]
@example
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
# File lib/fear/future.rb, line 376 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 do |other_value| if block_given? yield(value, other_value) else [value, other_value] end end, ) end end m.failure do |error| promise.failure!(error) end end promise.to_future end