# Promise and Future Patterns in the aperturerobotics/util Go Library: Use Cases and Implementation

> Discover aperturerobotics/util Go library's Promise and Future patterns. Handle async results, context cancellation, and memoized initialization efficiently in your Go applications.

- Repository: [Aperture Robotics/util](https://github.com/aperturerobotics/util)
- Tags: use-cases
- Published: 2026-02-25

---

**The `aperturerobotics/util` library implements type-safe Promise and Future patterns to handle asynchronous results with context cancellation, providing single-use promises, swappable containers, and memoized initialization for Go applications.**

The `aperturerobotics/util` repository offers a lightweight, zero-dependency implementation of Promise and Future patterns designed specifically for Go’s concurrency model. Unlike generic future implementations, this library emphasizes **context-aware cancellation**, **atomic result setting**, and **composability** through a shared `PromiseLike` interface. The patterns enable goroutines to produce values asynchronously while allowing callers to await results with proper timeout and cancellation handling.

## Core Components of the Promise/Future Abstraction

The library organizes its Promise and Future functionality into distinct types, each optimized for specific concurrency patterns. All components implement the `PromiseLike[T]` interface defined in [`promise/like.go`](https://github.com/aperturerobotics/util/blob/main/promise/like.go), ensuring uniform access to the `Await(context.Context) (T, error)` method.

### Promise[T] for Single-Use Asynchronous Results

The `Promise[T]` type in [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go) represents a concrete, single-use asynchronous result. It maintains a `done` channel, the result value or error, and an `atomic.Bool` flag that guarantees the result is set at most once.

Use `Promise[T]` when you need:
- One-off async operations where the producer sets a value exactly once (network requests, background computations)
- Blocking behavior that waits until the result is ready **or** the context is cancelled

Key implementation details from [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go):
- Creation via `NewPromise[T]()` (lines 20-22)
- Result setting via `SetResult(value T, err error)` with atomic guards (lines 46-53)
- Awaiting via `Await(ctx)` which checks `ctx.Done()` before blocking (lines 56-63)

### PromiseContainer[T] for Mutable Futures

The `PromiseContainer[T]` type in [`promise/container.go`](https://github.com/aperturerobotics/util/blob/main/promise/container.go) acts as a mutable holder that can swap the underlying `Promise`. Unlike the fixed `Promise[T]`, containers allow long-running services to replace pending results as state changes occur.

Use `PromiseContainer[T]` when you need:
- Long-running services that must replace the pending result (routines that restart on state changes)
- Scenarios where multiple consumers "watch" a result that may be reproduced multiple times

The container uses the internal `broadcast.Broadcast` primitive to notify waiters when the underlying promise has been swapped (see `SetPromise` and `GetPromise` in [`container.go`](https://github.com/aperturerobotics/util/blob/main/container.go) lines 29-33 and 50-56).

### PromiseLike[T] Interface for Composability

All Promise and Future types implement `PromiseLike[T]`, defined in [`promise/like.go`](https://github.com/aperturerobotics/util/blob/main/promise/like.go). This interface requires only `Await(context.Context) (T, error)`, allowing any component to treat promises, containers, and reference-counted futures uniformly.

## Practical Use Cases for Promise and Future Patterns

The library demonstrates several architectural patterns where Promise and Future abstractions solve specific concurrency challenges in Go applications.

### One-off Async Operations with Promise[T]

For simple asynchronous work that produces a single result, use the raw `Promise[T]` type. This pattern blocks callers until completion while respecting context cancellation.

```go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aperturerobotics/util/promise"
)

func asyncWork(ctx context.Context, p *promise.Promise[int]) {
	// Simulate work.
	time.Sleep(2 * time.Second)

	// Resolve the promise (only succeeds once).
	p.SetResult(42, nil)
}

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

	p := promise.NewPromise[int]()
	go asyncWork(ctx, p)

	val, err := p.Await(ctx)
	if err != nil {
		fmt.Println("failed:", err)
		return
	}
	fmt.Println("result:", val) // → 42
}

```

*Key lines*: creation (`NewPromise`) – [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go) lines 20-22; setting result (`SetResult`) – [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go) lines 46-53; awaiting (`Await`) – [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go) lines 56-63.

### Long-running Services with PromiseContainer[T]

When building services that restart or update their state, `PromiseContainer[T]` allows you to swap the future being awaited without recreating consumer goroutines.

```go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aperturerobotics/util/promise"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ctr := promise.NewPromiseContainer[string]()

	// Consumer: awaits the *current* promise.
	go func() {
		for {
			p, wait := ctr.GetPromise()
			if p == nil {
				<-wait // wait for a promise to be set
				continue
			}
			val, err := p.Await(ctx)
			if err != nil {
				fmt.Println("await error:", err)
				return
			}
			fmt.Println("got:", val)
			return
		}
	}()

	// Producer: after a delay replace the promise with a resolved one.
	time.Sleep(1 * time.Second)
	ctr.SetResult("hello world", nil) // internally creates a new Promise
	// Output: got: hello world
}

```

*Key functions*: `NewPromiseContainer` – [`promise/container.go`](https://github.com/aperturerobotics/util/blob/main/promise/container.go) lines 20-22; `SetResult` – [`promise/container.go`](https://github.com/aperturerobotics/util/blob/main/promise/container.go) lines 50-56; `GetPromise` – [`promise/container.go`](https://github.com/aperturerobotics/util/blob/main/promise/container.go) lines 29-33.

### Memoized Initialization with Once[T]

The `Once[T]` type provides "run-once-with-retry" semantics. It memoizes successful results in a `Promise` and discards the promise on error, allowing subsequent calls to retry.

Use this for expensive initialization that should happen only once per successful run, such as establishing connections or loading caches, while still presenting a `Future`-like API.

```go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aperturerobotics/util/promise"
)

func main() {
	ctx := context.Background()

	once := promise.NewOnce(func(ctx context.Context) (int, error) {
		fmt.Println("expensive init running")
		time.Sleep(1 * time.Second)
		return 123, nil // success
	})

	// First caller triggers the work.
	val, _ := once.Resolve(ctx)
	fmt.Println("first:", val)

	// Second caller gets the memoized result instantly.
	val, _ = once.Resolve(ctx)
	fmt.Println("second:", val)
}

```

*Key source*: `NewOnce` – [`promise/once.go`](https://github.com/aperturerobotics/util/blob/main/promise/once.go) lines 20-22; the internal loop that retries on error – [`promise/once.go`](https://github.com/aperturerobotics/util/blob/main/promise/once.go) lines 64-71.

### Publishing Routine Results via PromiseContainer

The `routine.NewStateResultRoutine` function builds a `StateRoutine` that automatically publishes its result into a `PromiseContainer`. This pattern turns any state-driven routine into a component that other system parts can await without tight coupling.

```go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aperturerobotics/util/promise"
	"github.com/aperturerobotics/util/routine"
)

func producer(ctx context.Context, st int) (string, error) {
	// Simulate work based on state.
	time.Sleep(500 * time.Millisecond)
	return fmt.Sprintf("state %d complete", st), nil
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Build a routine that automatically writes its result into a container.
	stateR, resultCtr := routine.NewStateResultRoutine[int, string](producer)

	// Run the routine in its own goroutine (the library’s StateRoutine manager would normally do this).
	go func() {
		_ = stateR(ctx, 7) // ignore the routine’s own error for demo
	}()

	// Await the result via the container (future‑style).
	val, err := resultCtr.Await(ctx)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("routine result:", val) // → "state 7 complete"
}

```

*Relevant source*: `NewStateResultRoutine` – [`routine/result.go`](https://github.com/aperturerobotics/util/blob/main/routine/result.go) lines 17-22; result is stored with `SetPromise` and later `SetResult` (lines 30-38).

### Reference-Counted Resources with AddRefPromise

The `refcount.RefCount[T]` type provides `AddRefPromise`, which returns a `PromiseLike[T]` together with a reference object. The promise fulfills when the ref-counted value resolves and clears when the last reference goes away. This supports resource-sharing patterns where callers need a future value but also deterministic release semantics.

```go
package main

import (
	"context"
	"fmt"

	"github.com/aperturerobotics/util/refcount"
)

func resolver(ctx context.Context, released func()) (string, func(), error) {
	// Provide a value and a release hook.
	return "shared‑value", func() { fmt.Println("resource released") }, nil
}

func main() {
	ctx := context.Background()
	rc := refcount.NewRefCount[string](ctx, false, nil, nil, resolver)

	prom, ref := rc.AddRefPromise() // ← returns a PromiseLike
	val, err := prom.Await(ctx)
	if err != nil {
		panic(err)
	}
	fmt.Println("got:", val) // → shared‑value

	// When we are done:
	ref.Release() // triggers the release hook when the last ref goes away
}

```

*Key source*: `AddRefPromise` – [`refcount/refcount.go`](https://github.com/aperturerobotics/util/blob/main/refcount/refcount.go) lines 165-176; the `PromiseContainer` used to bridge the ref-counted value to a future.

## Implementation Highlights from Source Code

The Promise and Future patterns in `aperturerobotics/util` distinguish themselves through several architectural decisions visible in the source code.

### Context-Aware Cancellation

Every `Await` variant checks `ctx.Done()` before blocking, ensuring that awaiting goroutines abort promptly without leaking resources. This aligns with Go’s standard context propagation patterns.

### Atomic Once-Only Semantics

In [`promise/promise.go`](https://github.com/aperturerobotics/util/blob/main/promise/promise.go), the `Promise.isDone` field is an `atomic.Bool` guaranteeing that `SetResult` cannot overwrite a completed promise (see lines 46-49). This prevents race conditions in concurrent environments.

### Broadcast-Driven Replacement

`PromiseContainer` uses the internal `broadcast.Broadcast` primitive to notify waiters when the underlying promise has been swapped. This enables dynamic replacement of futures without recreating consumer logic (see `SetPromise` and `GetPromise` in [`promise/container.go`](https://github.com/aperturerobotics/util/blob/main/promise/container.go) lines 29-33 and 50-56).

### Composable Higher-Level Utilities

The `RefCount.AddRefPromise` method and `NewStateResultRoutine` function demonstrate how the promise abstraction composes with reference counting and state-driven goroutine orchestration, respectively.

## Summary

The Promise and Future patterns in `aperturerobotics/util` provide deterministic, composable asynchronous result handling for Go applications:

- **`Promise[T]`** delivers single-use futures for one-off async operations with atomic result setting and context cancellation support.
- **`PromiseContainer[T]`** enables swappable futures for long-running services that restart or update their state dynamically.
- **`Once[T]`** provides memoized initialization with automatic retry semantics for expensive setup operations.
- **`PromiseLike[T]`** unifies all future types under a single interface for generic consumption.
- **Integration points** like `routine.NewStateResultRoutine` and `refcount.AddRefPromise` demonstrate real-world composition with goroutine management and resource lifecycle control.

## Frequently Asked Questions

### What is the difference between Promise[T] and PromiseContainer[T] in the util library?

`Promise[T]` is a single-use future that, once resolved with `SetResult`, cannot be changed or reused. It is ideal for one-off computations like network requests. `PromiseContainer[T]` acts as a mutable holder that can swap its underlying `Promise` via `SetPromise` or `SetResult`, making it suitable for long-running services that restart or update their asynchronous state dynamically.

### How does the util library handle context cancellation with futures?

Every future implementation in the library, including `Promise[T]`, `PromiseContainer[T]`, and `Once[T]`, implements the `PromiseLike[T]` interface which requires an `Await(context.Context)` method. Before blocking, these implementations check `ctx.Done()` to ensure goroutines can be aborted promptly without resource leaks, aligning with Go’s standard context propagation patterns.

### When should I use Once[T] instead of a standard Promise[T]?

Use `Once[T]` when you need **memoized initialization with automatic retry semantics**. Unlike a raw `Promise[T]`, which resolves once and stays resolved, `Once[T]` runs a callback lazily, stores the successful result in an internal `Promise`, and discards the promise on error. This allows subsequent calls to `Resolve` to retry the initialization, making it ideal for expensive setup operations like establishing database connections that might fail initially.

### How does PromiseContainer[T] notify consumers when the underlying promise is swapped?

`PromiseContainer[T]` uses an internal `broadcast.Broadcast` primitive to signal waiters when the underlying promise changes. When `SetPromise` or `SetResult` is called, the container broadcasts the update, allowing consumers blocked on `GetPromise` to wake up and retrieve the new future. This mechanism enables dynamic replacement of futures without requiring consumers to poll or recreate their waiting logic.