class Async::Promise
@public Since *Async v2*.
This is thread-safe and integrates with the fiber scheduler.
with the stored value or raise the stored exception.
Unlike Condition, once resolved (or rejected), all future waits return immediately
A promise represents a value that will be available in the future.
def self.fulfill(promise, &block)
@yields {...} The block to call to resolve the promise or return a value.
@parameter promise [Promise | Nil] The optional promise to fulfill.
This is useful for methods that may optionally take a promise to fulfill.
If no promise is given, simply yield to the block.
If a promise is given, fulfill it with the result of the block.
def self.fulfill(promise, &block) if promise return promise.fulfill(&block) else return yield end end
def cancel(exception = Cancel.new("Promise cancelled!"))
All current and future waiters will receive nil.
Cancel the promise, indicating cancellation.
def cancel(exception = Cancel.new("Promise cancelled!")) @mutex.synchronize do # No-op if already in any final state return if @resolved @resolved = :cancelled @value = exception # Wake up all waiting fibers: @condition.broadcast end return nil end
def cancelled?
def cancelled? @mutex.synchronize{@resolved == :cancelled} end
def completed?
def completed? @mutex.synchronize{@resolved == :completed} end
def failed?
def failed? @mutex.synchronize{@resolved == :failed} end
def fulfill(&block)
@yields {...} The block to call to resolve the promise.
If the promise was already resolved, the block will not be called.
If the block raises an exception, the promise will be rejected.
Resolve the promise with the result of the block.
def fulfill(&block) raise "Promise already resolved!" if @resolved begin return self.resolve(yield) rescue Cancel => exception return self.cancel(exception) rescue => error return self.reject(error) rescue Exception => exception self.reject(exception) raise ensure # Handle non-local exits (throw, etc.) that bypass normal flow: self.resolve(nil) unless @resolved end end
def initialize
def initialize # nil = pending, :completed = success, :failed = failure, :cancelled = cancelled: @resolved = nil # Stores either the result value or the exception: @value = nil # Track how many fibers are currently waiting: @waiting = 0 @mutex = Mutex.new @condition = ConditionVariable.new end
def reject(exception)
Can only be called once - subsequent calls are ignored.
All current and future waiters will receive this exception.
Reject the promise with an exception.
def reject(exception) @mutex.synchronize do return if @resolved @resolved = :failed @value = exception # Wake up all waiting fibers: @condition.broadcast end return nil end
def resolve(value)
Can only be called once - subsequent calls are ignored.
All current and future waiters will receive this value.
Resolve the promise with a value.
def resolve(value) @mutex.synchronize do return if @resolved @resolved = :completed @value = value # Wake up all waiting fibers: @condition.broadcast end return value end
def resolved
- Private: - For internal use by Task.
def resolved @mutex.synchronize{@resolved} end
def resolved?
def resolved? @mutex.synchronize{!!@resolved} end
def suppress_warnings!
- Private: - Internal use only.
def suppress_warnings! @mutex.synchronize{@waiting += 1} end
def value
For resolved promises, returns the raw stored value (result, exception, or cancel exception).
Does not raise exceptions even if the promise was rejected or cancelled.
Non-blocking access to the current value. Returns nil if not yet resolved.
def value @mutex.synchronize{@resolved ? @value : nil} end
def wait(...)
@raises [Exception] The rejected or cancelled exception.
@returns [Object] The resolved value.
If already resolved, returns immediately. If rejected, raises the stored exception.
Wait for the promise to be resolved and return the value.
def wait(...) resolved = wait?(...) if resolved.nil? raise TimeoutError, "Timeout while waiting for promise!" elsif resolved == :completed return @value elsif @value # If we aren't completed, we should have an exception or cancel reason stored: raise @value end end
def wait?(timeout: nil)
@parameter timeout [Numeric | Nil] Maximum time to wait. If nil, waits indefinitely. If 0, returns immediately if not resolved.
If already resolved, returns immediately. Otherwise, waits until resolution or timeout.
Wait for the promise to be resolved (without raising exceptions).
def wait?(timeout: nil) unless @resolved @mutex.synchronize do # Increment waiting count: @waiting += 1 begin # Wait for resolution if not already resolved: unless @resolved if timeout.nil? wait_indefinitely else unless wait_with_timeout(timeout) # We don't want to race on @resolved after exiting the mutex: return nil end end end ensure # Decrement waiting count when done: @waiting -= 1 end end end return @resolved end
def wait_indefinitely
def wait_indefinitely resolved tion.wait(@mutex)
def wait_with_timeout(timeout)
@parameter timeout [Numeric] The timeout duration.
Wait for the promise to be resolved, respecting the deadline timeout.
def wait_with_timeout(timeout) e deadline for timeout tracking: e = Deadline.start(timeout) e immediate timeout (non-blocking): line == Deadline::Zero && !@resolved false with deadline tracking: if @resolved remaining time for this wait iteration: ing = deadline.remaining k if deadline has expired before waiting: aining <= 0 n false tion.wait(@mutex, remaining) true
def waiting?
def waiting? @mutex.synchronize{@waiting > 0} end