async

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 8, 2024 License: Apache-2.0 Imports: 6 Imported by: 1

README

Fillmore Labs Async (Experimental)

Go Reference Build Status GitHub Workflow Test Coverage Maintainability Go Report Card License

The async package provides interfaces and utilities for writing asynchronous code in Go.

Motivation

Futures and promises are constructs used for asynchronous and concurrent programming, allowing developers to work with values that may not be immediately available and can be evaluated in a different execution context.

Go is known for its built-in concurrency features like goroutines and channels. The select statement further allows for efficient multiplexing and synchronization of multiple channels, thereby enabling developers to coordinate and orchestrate asynchronous operations effectively. Additionally, the context package offers a standardized way to manage cancellation, deadlines, and timeouts within concurrent and asynchronous code.

On the other hand, Go's error handling mechanism, based on explicit error values returned from functions, provides a clear and concise way to handle errors.

The purpose of this package is to provide a thin layer over channels which simplifies the integration of concurrent code while providing a cohesive strategy for handling asynchronous errors. By adhering to Go's standard conventions for asynchronous and concurrent code, as well as error propagation, this package aims to enhance developer productivity and code reliability in scenarios requiring asynchronous operations.

Usage

Assuming you have a synchronous function func getMyIP(ctx context.Context) (string, error) returning your external IP address (see GetMyIP for an example).

Now you can do

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	future := async.NewFutureAsync(func() (string, error) {
		return getMyIP(ctx)
	})

and elsewhere in your program, even in a different goroutine

	if ip, err := future.Wait(ctx); err == nil {
		slog.Info("Found IP", "ip", ip)
	} else {
		slog.Error("Failed to fetch IP", "error", err)
	}

decoupling query construction from result processing.

GetMyIP

Sample code to retrieve your IP address:

const serverURL = "https://httpbin.org/ip"

func getMyIP(ctx context.Context) (string, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, serverURL, nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Accept", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer func() { _ = resp.Body.Close() }()

	ipResponse := &struct {
		Origin string `json:"origin"`
	}{}

	if err := json.NewDecoder(resp.Body).Decode(ipResponse); err != nil {
		return "", err
	}

	return ipResponse.Origin, nil
}

Concurrency Correctness

When utilizing plain Go channels for concurrency, reasoning over the correctness of concurrent code becomes simpler compared to some other implementations of futures and promises. Channels provide a clear and explicit means of communication and synchronization between concurrent goroutines, making it easier to understand and reason about the flow of data and control in a concurrent program.

Therefore, this library provides a straightforward and idiomatic approach to achieving concurrency correctness.

Documentation

Overview

Package async provides interfaces and utilities for writing asynchronous code in Go.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoResult = errors.New("no result")

ErrNoResult is returned when a future completes but has no defined result value.

View Source
var ErrNotReady = errors.New("future not ready")

ErrNotReady is returned when a future is not complete.

Functions

func NewFuture

func NewFuture[R any]() (Future[R], Promise[R])

NewFuture provides a simple way to create a Future for synchronous operations. This allows synchronous and asynchronous code to be composed seamlessly and separating initiation from running.

The returned Future can be used to retrieve the eventual result of the Promise.

func Then

func Then[R, S any](ctx context.Context, f Awaitable[R], then func(R) (S, error)) (S, error)

Then transforms the embedded result from an Awaitable using 'then'. This allows to easily handle errors embedded in the response. It blocks until a result is received or the context is canceled.

func WaitAllValues added in v0.0.2

func WaitAllValues[R any](ctx context.Context, futures ...Awaitable[R]) ([]R, error)

WaitAllValues returns the values of all completed futures. If any future fails or the context is canceled, it returns early with an error.

func WaitFirst added in v0.0.2

func WaitFirst[R any](ctx context.Context, futures ...Awaitable[R]) (R, error)

WaitFirst returns the result of the first completed future. If the context is canceled, it returns early with an error.

func YieldAll added in v0.0.2

func YieldAll[R any](ctx context.Context, yield func(int, Result[R]) bool, futures ...Awaitable[R]) error

Types

type Awaitable

type Awaitable[R any] interface {
	Wait(ctx context.Context) (R, error) // Wait returns the final result of the associated [Promise].
	TryWait() (R, error)                 // TryWait returns the result when ready, [ErrNotReady] otherwise.
	Memoize() *Memoizer[R]               // Memoizer returns a [Memoizer] which can be queried multiple times.
	// contains filtered or unexported methods
}

Awaitable is the underlying interface for Future and Memoizer. Plain futures can only be queried once, while memoizers can be queried multiple times.

type Future

type Future[R any] <-chan Result[R]

Future represents an asynchronous operation that will complete sometime in the future.

It is a read-only channel that can be used to retrieve the final result of a Promise with Future.Wait.

func NewFutureAsync added in v0.0.2

func NewFutureAsync[R any](fn func() (R, error)) Future[R]

NewFutureAsync runs f asynchronously, immediately returning a Future that can be used to retrieve the eventual result. This allows separating evaluating the result from computation.

func ThenAsync

func ThenAsync[R, S any](ctx context.Context, f Awaitable[R], then func(R) (S, error)) Future[S]

ThenAsync asynchronously transforms the embedded result from an Awaitable using 'then'.

func (Future[R]) Memoize

func (f Future[R]) Memoize() *Memoizer[R]

Memoize creates a new Memoizer, consuming the Future.

func (Future[R]) TryWait added in v0.0.2

func (f Future[R]) TryWait() (R, error)

TryWait returns the result when ready, ErrNotReady otherwise.

func (Future[R]) Wait

func (f Future[R]) Wait(ctx context.Context) (R, error)

Wait returns the final result of the associated Promise. It can only be called once and blocks until a result is received or the context is canceled. If you need to read multiple times from a Future wrap it with Future.Memoize.

type Memoizer

type Memoizer[R any] struct {
	// contains filtered or unexported fields
}

Memoizer caches results from a Future to enable multiple queries and avoid unnecessary recomputation.

func (*Memoizer[R]) Memoize added in v0.0.2

func (m *Memoizer[R]) Memoize() *Memoizer[R]

Memoize returns this Memoizer.

func (*Memoizer[R]) TryWait

func (m *Memoizer[R]) TryWait() (R, error)

TryWait returns the cached result when ready, ErrNotReady otherwise.

func (*Memoizer[R]) Wait added in v0.0.2

func (m *Memoizer[R]) Wait(ctx context.Context) (R, error)

Wait returns the cached result or blocks until a result is available or the context is canceled.

type Promise

type Promise[R any] chan<- Result[R]

Promise is used to send the result of an asynchronous operation.

It is a write-only channel. Either Promise.Fulfill or Promise.Reject should be called exactly once.

func (Promise[R]) Do added in v0.0.2

func (p Promise[R]) Do(f func() (R, error))

Do runs f synchronously, fulfilling the promise once it completes.

func (Promise[R]) Fulfill added in v0.0.2

func (p Promise[R]) Fulfill(value R)

Fulfill fulfills the promise with a value.

func (Promise[R]) Reject added in v0.0.2

func (p Promise[R]) Reject(err error)

Reject breaks the promise with an error.

type Result

type Result[R any] interface {
	V() (R, error) // The V method returns the final value or an error.
	Value() R      // The Value method returns the final value.
	Err() error    // The Err method returns the error.
}

Result defines the interface for returning results from asynchronous operations. It encapsulates the final value or error from the operation.

func WaitAll added in v0.0.2

func WaitAll[R any](ctx context.Context, futures ...Awaitable[R]) ([]Result[R], error)

WaitAll returns the results of all completed futures. If the context is canceled, it returns early with an error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL