-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
75d6807
commit 7ba6caa
Showing
5 changed files
with
212 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package async | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
// Future is a proxy for a result that is initially unknown. | ||
// | ||
// Use `new(Future[T])` to create a new Future. Use the package level ResolveFuture | ||
// function to resolve a Latch. | ||
// | ||
// A Future must not be copied after first use. | ||
type Future[T any] struct { | ||
l Latch | ||
v T | ||
err error | ||
} | ||
|
||
// Done returns a channel that will be closed when the Future is resolved. | ||
func (fut *Future[T]) Done() <-chan struct{} { | ||
return fut.l.Done() | ||
} | ||
|
||
// Value blocks until the Future is resolved and returns resulting value and error. | ||
// | ||
// It is safe to call Value multiple times form multiple goroutines. | ||
func (fut *Future[T]) Value() (T, error) { | ||
Await(fut) | ||
return fut.v, fut.err | ||
} | ||
|
||
// ValueCtx blocks until the Future is resolved or the context is canceled. | ||
// | ||
// If the context is canceled, the returned error will be the context's error. A canceled | ||
// context does not necessarily mean that the Future was not resolved or will not resolve | ||
// in the future. | ||
// | ||
// It is safe to call ValueCtx multiple times form multiple goroutines. | ||
func (fut *Future[T]) ValueCtx(ctx context.Context) (T, error) { | ||
if err := AwaitCtx(ctx, fut); err != nil { | ||
var zero T | ||
return zero, err | ||
} | ||
return fut.v, fut.err | ||
} | ||
|
||
// ResolveFuture resolves a Future and sets its value and error. | ||
// | ||
// Resolving a Future more than once will panic with ErrAlreadyResolved. | ||
func ResolveFuture[T any](fut *Future[T], v T, err error) { | ||
Resolve(&fut.l, func() { | ||
fut.v = v | ||
fut.err = err | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package async_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ianlopshire/go-async" | ||
) | ||
|
||
func TestFuture_alreadyResolved(t *testing.T) { | ||
// When an already-resolved Future is resolved it should panic with ErrAlreadyResolved. | ||
defer func() { | ||
if recover() != async.ErrAlreadyResolved { | ||
t.Fatal("expected Future to panic with ErrAlreadyResolved") | ||
} | ||
}() | ||
|
||
fut := new(async.Future[string]) | ||
async.ResolveFuture(fut, "Hello World!", nil) | ||
async.ResolveFuture(fut, "Hello World!", nil) | ||
} | ||
|
||
func TestFuture_Value(t *testing.T) { | ||
for name, tt := range map[string]struct { | ||
v string | ||
err error | ||
}{ | ||
"with value": {"Hello World!", nil}, | ||
"with error": {"", errors.New("error")}, | ||
} { | ||
t.Run(name, func(t *testing.T) { | ||
fut := new(async.Future[string]) | ||
async.ResolveFuture(fut, tt.v, tt.err) | ||
|
||
v, err := fut.Value() | ||
if err != tt.err { | ||
t.Fatalf("Value() unexpected error have %v, want %v", err, tt.err) | ||
} | ||
if v != tt.v { | ||
t.Fatalf("Value() unexpected value have %v, want %v", v, tt.v) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestFuture_ValueCtx(t *testing.T) { | ||
for name, tt := range map[string]struct { | ||
v string | ||
err error | ||
}{ | ||
"with value": {"Hello World!", nil}, | ||
"with error": {"", errors.New("error")}, | ||
} { | ||
t.Run(name, func(t *testing.T) { | ||
fut := new(async.Future[string]) | ||
async.ResolveFuture(fut, tt.v, tt.err) | ||
|
||
v, err := fut.ValueCtx(context.Background()) | ||
if err != tt.err { | ||
t.Fatalf("Value() unexpected error have %v, want %v", err, tt.err) | ||
} | ||
if v != tt.v { | ||
t.Fatalf("Value() unexpected value have %v, want %v", v, tt.v) | ||
} | ||
}) | ||
} | ||
|
||
t.Run("with canceled context", func(t *testing.T) { | ||
timeout := time.After(time.Second) | ||
done := make(chan bool) | ||
|
||
go func() { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
cancel() | ||
|
||
fut := new(async.Future[string]) | ||
_, err := fut.ValueCtx(ctx) | ||
if err != context.Canceled { | ||
t.Errorf("ValueCtx() unexpected error have %v, want %v", err, context.Canceled) | ||
} | ||
|
||
done <- true | ||
}() | ||
|
||
select { | ||
case <-timeout: | ||
t.Fatal("ValueCtx() future should not have blocked") | ||
case <-done: | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
module github.com/ianlopshire/go-async | ||
|
||
go 1.13 | ||
go 1.18 |