diff --git a/lib/async/promise.rb b/lib/async/promise.rb new file mode 100644 index 0000000..c004014 --- /dev/null +++ b/lib/async/promise.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Patrik Wenger. + +require_relative "variable" + +module Async + + # Similar to a Variable, in that it allows a task to wait for a value to resolve, but also supports rejection given + # an error. The given error is raised as an exception in the waiting task. + class Promise < Variable + def initialize(...) + super + + @error = nil + end + + def reject(error = RuntimeError.new('promise rejected'), value = nil) + @error = error + resolve value + end + + def rejected? + resolved? && !!@error + end + + # @returns [Boolean] Whether the value has been resolved. + # @raises [Exception] The error with which this Variable was rejected + def wait + value = super + raise @error if @error + return value + end + end +end diff --git a/readme.md b/readme.md index 817c143..523ca62 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ Async is a composable asynchronous I/O framework for Ruby based on [io-event](ht > [tus-ruby-server](https://github.com/janko/tus-ruby-server) – would really benefit from non-blocking I/O. It's really > beautifully designed." *– [janko](https://github.com/janko)* -[![Development Status](https://github.com/socketry/async/workflows/Test/badge.svg)](https://github.com/socketry/async/actions?workflow=Test) +[![Development Status](https://github.com/socketry/async/workflows/Test/badge.svg)](https://github.com/socketry/async/actions?workflow=Test) [](https://api.gitsponsors.com/api/badge/link?p=U4gCxvzG7eUksiJSe0MSlN/IQp/rOEmM4wKYQwPrkiFn78vaBdtOdFQLsJu4pzyegaVzeek/1axVEWxd6fq1Ww==) ## Features diff --git a/test/async/promise.rb b/test/async/promise.rb new file mode 100644 index 0000000..68c92d1 --- /dev/null +++ b/test/async/promise.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2025, by Patrik Wenger. + +require "sus/fixtures/async" +require "async/promise" + +PromiseContext = Sus::Shared("a promise") do + let(:promise) {Async::Promise.new} + + it "can resolve the value" do + promise.resolve(value) + expect(promise).to be(:resolved?) + end + + it "can wait for the value to be resolved" do + Async do + expect(promise.wait).to be == value + end + + promise.resolve(value) + end + + it "can wait for the value to be resolved using setter" do + Async do + expect(promise.wait).to be == value + end + + promise.value = value + end + + it "can't resolve it a 2nd time" do + promise.resolve(value) + expect do + promise.resolve(value) + end.to raise_exception(FrozenError) + end + + it "can reject with an exception" do + promise.reject RuntimeError.new("boom") + expect(promise).to be(:resolved?) + expect(promise).to be(:rejected?) + end + + it "can wait for a rejection with an error" do + Async do + expect do + promise.wait + end.to raise_exception(RuntimeError) + end + + promise.reject(RuntimeError.new) + end +end + +include Sus::Fixtures::Async::ReactorContext + +describe true do + let(:value) {subject} + it_behaves_like PromiseContext +end + +describe false do + let(:value) {subject} + it_behaves_like PromiseContext +end + +describe nil do + let(:value) {subject} + it_behaves_like PromiseContext +end + +describe Object do + let(:value) {subject.new} + it_behaves_like PromiseContext +end diff --git a/test/async/variable.rb b/test/async/variable.rb index b8cdfd9..27d2517 100644 --- a/test/async/variable.rb +++ b/test/async/variable.rb @@ -21,6 +21,14 @@ variable.resolve(value) end + + it "can wait for the value to be resolved using setter" do + Async do + expect(variable.wait).to be == value + end + + variable.value = value + end it "can't resolve it a 2nd time" do variable.resolve(value)