async

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jan 31, 2024 License: Apache-2.0 Imports: 2 Imported by: 1

README

Async (Experimental)

Go Reference Build Status 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(), timeout)
	defer cancel()

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

and elsewhere in your program, even in a different goroutine

	if ip, err := future.Wait(ctx); err == nil {
		log.Printf("My IP is %s", ip)
	} else {
		log.Printf("Error fetching IP: %v", err)
	}

decoupling query construction from result processing.

GetMyIP

Sample code to retrieve your IP address:

const (
	serverURL = "https://httpbin.org/ip"
	timeout   = 2 * time.Second
)

type IPResponse struct {
	Origin string `json:"origin"`
}

func getMyIP(ctx context.Context) (string, error) {
	resp, err := sendRequest(ctx)
	if err != nil {
		return "", err
	}

	ipResponse, err := decodeResponse(resp)
	if err != nil {
		return "", err
	}

	return ipResponse.Origin, nil
}

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

	return http.DefaultClient.Do(req)
}

func decodeResponse(response *http.Response) (*IPResponse, error) {
	body, err := io.ReadAll(response.Body)
	_ = response.Body.Close()
	if err != nil {
		return nil, err
	}

	ipResponse := &IPResponse{}
	err = json.Unmarshal(body, ipResponse)
	if err != nil {
		return nil, err
	}

	return ipResponse, 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 utilities for handling asynchronous operations and results.

Index

Constants

This section is empty.

Variables

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

Functions

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.

Types

type Awaitable

type Awaitable[R any] interface {
	Wait(ctx context.Context) (R, error)
}

Awaitable is the underlying interface for Future and Memoizer. It blocks until a result is received or the context is canceled. 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 NewAsyncFuture

func NewAsyncFuture[R any](f func() (R, error)) Future[R]

NewAsyncFuture 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 NewFuture

func NewFuture[R any](f func(promise Promise[R])) Future[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 waiting.

- f takes a func that accepts a Promise as a Promise

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

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]) 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] interface {
	Awaitable[R]

	// TryWait returns the cached result when ready, [ErrNotReady] otherwise.
	TryWait() (R, error)
}

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

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.SendValue or Promise.SendError should be called exactly once.

func (Promise[R]) Send

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

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

func (Promise[R]) SendError

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

SendError breaks the promise with an error.

func (Promise[R]) SendValue

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

SendValue fulfills the promise with a value once the operation completes.

type Result

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

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

Jump to

Keyboard shortcuts

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