Class: Fear::Future

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

Overview

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

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.

Examples:

success = "Hello"
f = Fear.future { success + ' future!' }
f.on_success do |result|
  puts result
end
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

See Also:

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.

Parameters:

  • promise (nil, Concurrent::Future) (defaults to: nil)

    converts Concurrent::Future into Fear::Future.

  • options (see Concurrent::Future)

    options will be passed directly to Concurrent::Future

Yields:

  • given block and evaluate it in the future.



47
48
49
50
51
52
53
54
55
56
# File 'lib/fear/future.rb', line 47

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.

Parameters:

  • exception (StandardError)

Returns:



397
398
399
400
401
# File 'lib/fear/future.rb', line 397

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.

Parameters:

  • result (Object)

Returns:



407
408
409
410
411
# File 'lib/fear/future.rb', line 407

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

Instance Method Details

#and_then(&block) ⇒ Object

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
  fail 'runtime error'
end.and_then do |m|
  m.success { |value| puts value } # it evaluates this branch
  m.failure { |error| puts error.massage }
end


382
383
384
385
386
387
388
389
390
# File 'lib/fear/future.rb', line 382

def and_then(&block)
  promise = Promise.new(@options)
  on_complete do |try|
    Fear.try { try.match(&block) }
    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

Returns:

  • (true, false)

    true if the future is already completed, false otherwise.



134
135
136
# File 'lib/fear/future.rb', line 134

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.

rubocop: disable Metrics/MethodLength

Examples:

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

Parameters:

Returns:



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/fear/future.rb', line 342

def fallback_to(fallback)
  promise = Promise.new(@options)
  on_complete do |try|
    try.match do |m|
      m.success { |value| promise.complete!(value) }
      m.failure do |error|
        fallback.on_complete do |fallback_try|
          fallback_try.match do |m2|
            m2.success { |value| promise.complete!(value) }
            m2.failure { promise.failure!(error) }
          end
        end
      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)

Returns:



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/fear/future.rb', line 225

def flat_map # rubocop: disable Metrics/MethodLength
  promise = Promise.new(@options)
  on_complete do |result|
    result.match do |m|
      m.failure { promise.complete!(result) }
      m.success do |value|
        begin
          yield(value).on_complete { |callback_result| promise.complete!(callback_result) }
        rescue StandardError => error
          promise.failure!(error)
        end
      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 }

Returns:



199
200
201
202
203
204
205
206
# File 'lib/fear/future.rb', line 199

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:

Returns:

  • (self)


115
116
117
118
119
120
# File 'lib/fear/future.rb', line 115

def on_complete
  promise.add_observer do |_time, try, _error|
    yield try
  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)

Returns:

  • (self)


95
96
97
98
99
100
101
# File 'lib/fear/future.rb', line 95

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

#on_success {|| ... } ⇒ 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:

Returns:

  • (self)

See Also:



73
74
75
76
77
# File 'lib/fear/future.rb', line 73

def on_success(&block)
  on_complete do |result|
    result.each(&block)
  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

Returns:



283
284
285
286
287
288
289
290
# File 'lib/fear/future.rb', line 283

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)

Returns:



259
260
261
262
263
264
265
266
267
# File 'lib/fear/future.rb', line 259

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

Returns:

  • (Fear::Future)

    a future that will be completed with the transformed value



178
179
180
181
182
183
184
185
186
187
# File 'lib/fear/future.rb', line 178

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

#valueFear::Option<Fear::Try>

The value of this Future.

Returns:

  • (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.



146
147
148
# File 'lib/fear/future.rb', line 146

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

Parameters:

Returns:



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/fear/future.rb', line 309

def zip(other) # rubocop: disable Metrics/MethodLength
  promise = Promise.new(@options)
  on_complete do |try_of_self|
    try_of_self.match do |m|
      m.success do |value|
        other.on_complete do |try_of_other|
          promise.complete!(try_of_other.map { |other_value| [value, other_value] })
        end
      end
      m.failure do |error|
        promise.failure!(error)
      end
    end
  end

  promise.to_future
end