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)

@returns [Object] The result of the 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 was cancelled!"))

Can only be called on pending promises - no-op if already resolved.
All current and future waiters will receive nil.
Cancel the promise, indicating cancellation.
def cancel(exception = Cancel.new("Promise was cancelled!"))
	@mutex.synchronize do
		# No-op if already in any final state
		return if @resolved
		
		@value = exception
		@resolved = :cancelled
		
		# Wake up all waiting fibers:
		@condition.broadcast
	end
	
	return nil
end

def cancelled?

@returns [Boolean] Whether the promise has been cancelled.
def cancelled?
	@mutex.synchronize {@resolved == :cancelled}
end

def completed?

@returns [Boolean] Whether the promise has completed successfully.
def completed?
	@mutex.synchronize {@resolved == :completed}
end

def failed?

@returns [Boolean] Whether the promise failed with an exception.
def failed?
	@mutex.synchronize {@resolved == :failed}
end

def fulfill(&block)

@returns [Object] The result of the 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

Create a new promise.
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)

@parameter exception [Exception] The exception to reject the promise with.

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
		
		@value = exception
		@resolved = :failed
		
		# Wake up all waiting fibers:
		@condition.broadcast
	end
	
	return nil
end

def resolve(value)

@parameter value [Object] The value to resolve the promise with.

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
		
		@value = value
		@resolved = :completed
		
		# Wake up all waiting fibers:
		@condition.broadcast
	end
	
	return value
end

def resolved

Other tags:
    Private: - For internal use by Task.
def resolved
	@mutex.synchronize {@resolved}
end

def resolved?

@returns [Boolean] Whether the promise has been resolved or rejected.
def resolved?
	@mutex.synchronize {!!@resolved}
end

def suppress_warnings!

Other tags:
    Private: - Internal use only.
def suppress_warnings!
	@mutex.synchronize {@waiting += 1}
end

def value

@returns [Object | Nil] The stored value, or nil if pending.

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
	@mutex.synchronize do
		# Increment waiting count:
		@waiting += 1
		
		begin
			# Wait for resolution if not already resolved:
			@condition.wait(@mutex) unless @resolved
			
			# Return value or raise exception based on resolution type:
			if @resolved == :completed
				return @value
			else
				# Both :failed and :cancelled store exceptions in @value
				raise @value
			end
		ensure
			# Decrement waiting count when done:
			@waiting -= 1
		end
	end
end

def waiting?

@returns [Boolean] Whether any fibers are currently waiting for this promise.
def waiting?
	@mutex.synchronize {@waiting > 0}
end