Skip to content

Commit

Permalink
Improve examples and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ianlopshire committed Jul 15, 2020
1 parent 266775c commit f680957
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 57 deletions.
83 changes: 37 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,86 +11,77 @@ Package `async` provides asynchronous primitives and utilities.
`Future` is the main primitive provided by `async`.

```go
// Future is a synchronization primitive that guards data that may be available in the
// future.
//
// NewFuture() is the preferred method of creating a new Future.
type Future interface {

// Done returns a channel that is closed when the Future is resolved. It is safe to
// call Done multiple times across multiple threads.
Done() <-chan struct{}
}
```

The provided `Future` primitive does not act as a container for data, but guards
data that will be available in the future.
The provided `Future` primitive does not act as a container for data, but guards data that will be available in the future.

```go
// Define a place where data will be stored.
var (
value string
err error
)

// Create a new Future.
future, resolve := async.NewFuture()
// Define a value that will be available in the future.
var val string
fut, resolve := async.NewFuture()

// Simulate long computation or IO by sleeping before setting the value and resolving
// the future.
go func() {
// Sleep to simulate a cpu intensive task or network request.
time.Sleep(500 * time.Millisecond)
value = "Hello World!"

// Resolve the future now that the data has been stored.
val = "Hello World!"
resolve()
}()

// Wait for the future to resolve.
<-future.Done()
// Block until the future is resolved.
async.Await(fut)

// Now that the future is resolved it is safe to use the data.
if err != nil {
log.Fatal(err)
}
fmt.Println(value)
fmt.Println(val)
```

In practice a `Future` can be embedded into a type to more closely match the traditional
behavior of a future.
In practice a `Future` can be embedded into a container to more closely match the traditional behavior of a future.

```go
type Container struct {
Future
Value string
async.Future
Value interface{}
Err error
}

func NewContainer() (*Container, func(string}, error)) {
f, r := NewFuture()

v := &Container{
Future: f,
func NewContainer() (*Container, func(interface{}, error)) {
fut, resolve := async.NewFuture()
container := &Container{
Future: fut,
}

return v, func(value string , err error) {
v.Value = value
v.Err = err
r()
fn := func(value interface{}, err error) {
container.Value = value
container.Err = err
resolve()
}

return container, fn
}

func DoSomething() {
// Create a container with an embedded Future.
func Example() {
// Define a value that will be available in the future.
container, resolve := NewContainer()

// Simulate long computation or IO by sleeping before and resolving the future.
go func() {
// Sleep to simulate a cpu intensive task or network request.
time.Sleep(500 * time.Millisecond)

// Resolve the container with some data.
resolve("Hello World!", nil)
}()

// Wait for the future to resolve.
Await(container)
// Block until the future is resolved.
async.Await(container)

// Now that the future is resolved it is safe to use the data.
if err := container.Err; err != nil {
log.Fatal(err)
}
fmt.Println(container.Err)
fmt.Println(container.Value, container.Err)
// output: Hello World! <nil>
}
```
9 changes: 8 additions & 1 deletion aync.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// Package async provides asynchronous primitives and utilities.
package async

// Await blocks until a future resolves.
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})

func init() {
close(closedchan)
}

// Await blocks until a future is resolved.
func Await(f Future) {
<-f.Done()
return
Expand Down
47 changes: 47 additions & 0 deletions example_container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package async_test

import (
"fmt"
"time"

"github.com/ianlopshire/go-async"
)

type Container struct {
async.Future
Value interface{}
Err error
}

func NewContainer() (*Container, func(interface{}, error)) {
fut, resolve := async.NewFuture()
container := &Container{
Future: fut,
}

fn := func(value interface{}, err error) {
container.Value = value
container.Err = err
resolve()
}

return container, fn
}

// This example demonstrates how a Future can be embedded into a container.
func ExampleFuture_container() {
// Define a value that will be available in the future.
container, resolve := NewContainer()

// Simulate long computation or IO by sleeping before and resolving the future.
go func() {
time.Sleep(500 * time.Millisecond)
resolve("Hello World!", nil)
}()

// Block until the future is resolved.
async.Await(container)

fmt.Println(container.Value, container.Err)
// output: Hello World! <nil>
}
58 changes: 58 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package async_test

import (
"fmt"
"time"

"github.com/ianlopshire/go-async"
)

func ExampleFuture() {
// Define a value that will be available in the future.
var val string
fut, resolve := async.NewFuture()

// Simulate long computation or IO by sleeping before setting the value and resolving
// the future.
go func() {
time.Sleep(500 * time.Millisecond)
val = "Hello World!"
resolve()
}()

// Block until the future is resolved.
async.Await(fut)

fmt.Println(val)
// output: Hello World!
}

func ExampleFuture_select() {
// Define a value that will be available in the future.
var val string
fut, resolve := async.NewFuture()

// The channel returned by Done() can be used directly in a select statement.
select {
case <-fut.Done():
fmt.Println(val)
default:
fmt.Println("Future not yet resolved")
}

// Simulate long computation or IO by sleeping before setting the value and resolving
// the future.
go func() {
time.Sleep(500 * time.Millisecond)
val = "Hello World!"
resolve()
}()

// Block until the future is resolved.
<-fut.Done()

fmt.Println(val)
// output:
// Future not yet resolved
// Hello World!
}
20 changes: 10 additions & 10 deletions future.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import (
"sync"
)

// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})

func init() {
close(closedchan)
}

// Future guards data that will be available at some point in the future.
// Future is a synchronization primitive that guards data that may be available in the
// future.
//
// NewFuture() is the preferred method of creating a new Future.
type Future interface {

// Done returns a channel that is closed when the Future is resolved. It is safe to
// call Done multiple times across multiple threads.
Done() <-chan struct{}
}

Expand All @@ -25,6 +24,8 @@ type future struct {
}

// NewFuture returns a new future and function that will resolve it.
//
// Calling resolve more than once will cause a panic.
func NewFuture() (Future, ResolveFunc) {
f := new(future)
return f, f.resolve
Expand All @@ -41,14 +42,13 @@ func (f *future) resolve() {

select {
case <-f.done:
panic("future is already resolved")
panic("async: future is already resolved")
default:
}

close(f.done)
}

// Done returns a channel that's closed once the future is resolved.
func (f *future) Done() <-chan struct{} {
f.mu.Lock()
if f.done == nil {
Expand Down

0 comments on commit f680957

Please sign in to comment.