{"id":23139596,"url":"https://github.com/jizhuozhi/go-future","last_synced_at":"2025-08-17T11:33:37.742Z","repository":{"id":248080158,"uuid":"827689551","full_name":"jizhuozhi/go-future","owner":"jizhuozhi","description":"Promise/Future in Go","archived":false,"fork":false,"pushed_at":"2024-11-04T03:04:56.000Z","size":31,"stargazers_count":35,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-04T04:17:44.906Z","etag":null,"topics":["future","go","promise","sync"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jizhuozhi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-12T07:08:51.000Z","updated_at":"2024-11-04T03:05:00.000Z","dependencies_parsed_at":"2024-07-31T11:48:33.199Z","dependency_job_id":"5c20fd34-11be-4f12-8ea2-677c7cd2583c","html_url":"https://github.com/jizhuozhi/go-future","commit_stats":null,"previous_names":["jizhuozhi/go-future"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jizhuozhi%2Fgo-future","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jizhuozhi%2Fgo-future/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jizhuozhi%2Fgo-future/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jizhuozhi%2Fgo-future/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jizhuozhi","download_url":"https://codeload.github.com/jizhuozhi/go-future/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230121616,"owners_count":18176477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["future","go","promise","sync"],"created_at":"2024-12-17T13:14:31.662Z","updated_at":"2025-08-17T11:33:37.728Z","avatar_url":"https://github.com/jizhuozhi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-future\n\n[![codecov](https://codecov.io/github/jizhuozhi/go-future/graph/badge.svg?token=9UZDVRZCQM)](https://codecov.io/github/jizhuozhi/go-future)\n[![goreport](https://goreportcard.com/badge/github.com/jizhuozhi/go-future)](https://goreportcard.com/badge/github.com/jizhuozhi/go-future)\n\n**go-future** is a lightweight, high-performance, lock-free Future/Promise implementation for Go, built with modern concurrency in mind. It supports:\n\n- Asynchronous task execution (`Async`, `CtxAsync`)\n- ~~Lazy evaluation (`Lazy`)~~(Deprecated, will be removed in later version)\n- Promise resolution (`Promise`)\n- Event-driven callback registration (`Subscribe`)\n- Functional chaining (`Then`, `ThenAsync`)\n- Task composition (`AllOf`, `AnyOf`)\n- Timeout control (`Timeout`, `Until`)\n- Full support for Go generics\n\n## 🔧 Installation\n\n```bash\ngo get github.com/jizhuozhi/go-future\n````\n\n---\n\n## 🚀 Quick Start\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/jizhuozhi/go-future\"\n)\n\nfunc main() {\n\tp := future.NewPromise[string]()\n\tgo func() {\n\t\tp.Set(\"hello\", nil)\n\t}()\n\tval, err := p.Future().Get()\n\tfmt.Println(val, err) // Output: hello \u003cnil\u003e\n}\n```\n\n---\n\n## 🧠 Core Concepts\n\n### Promise and Future\n\n* `Promise` is the **producer**, which sets the value once.\n* `Future` is the **consumer**, which retrieves the result asynchronously.\n\nEvery Future is backed by a lock-free internal state. All state transitions are safe and efficient under high concurrency.\n\n---\n\n## 🔨 Key APIs\n\n### `Async(func() (T, error)) *Future[T]`\n\nStarts a new asynchronous task in a goroutine.\n\n```go\nf := future.Async(func() (string, error) {\n\treturn \"result\", nil\n})\nval, err := f.Get()\n```\n\n---\n\n### `Lazy(func() (T, error)) *Future[T]`\n\nReturns a future that is lazily evaluated. The function is only executed once on the first call to `.Get()`.\n\n```go\nf := future.Lazy(func() (string, error) {\n\tfmt.Println(\"evaluated\")\n\treturn \"lazy\", nil\n})\nval, _ := f.Get() // prints \"evaluated\"\n```\n\n#### ⚠️ Deprecation Notice: Lazy\nThe Lazy API is deprecated and will be removed in future versions.\n\nAlthough Lazy provides deferred execution semantics, it introduces implicit pull-based dependencies that are difficult to reason about in practice. Unlike Async, where execution is guaranteed upon construction, Lazy defers execution until .Get() is called — often far away from the site of definition.\n\nThis subtle semantic difference:\n- Makes control flow harder to predict\n- Breaks intuitive data dependency modeling\n- Can result in unexpected bugs when chained or composed in concurrent settings\n\nRecommendation: Prefer using Async or Promise for all use cases. These are explicit and deterministic.\n\n---\n\n### `Promise[T]`\n\nUsed to create and control a future manually.\n\n```go\np := future.NewPromise[int]()\ngo func() {\n\tp.Set(42, nil)\n}()\nval, _ := p.Future().Get()\n```\n\n---\n\n### `Then(f *Future[T], cb func(T, error) (R, error)) *Future[R]`\n\nChains computations synchronously.\n\n```go\nf := future.Async(func() (int, error) { return 1, nil })\nf2 := future.Then(f, func(v int, err error) (string, error) {\n\treturn fmt.Sprintf(\"num:%d\", v), err\n})\nresult, _ := f2.Get()\n```\n\n---\n\n### `ThenAsync(f *Future[T], cb func(T, error) *Future[R]) *Future[R]`\n\nChains computations with asynchronous return.\n\n```go\nf := future.Async(func() (int, error) { return 1, nil })\nf2 := future.ThenAsync(f, func(v int, err error) *future.Future[string] {\n\treturn future.Async(func() (string, error) {\n\t\treturn fmt.Sprintf(\"async:%d\", v), nil\n\t})\n})\nresult, _ := f2.Get()\n```\n\n---\n\n### `AllOf(fs ...*Future[T]) *Future[[]T]`\n\nWaits for all futures to complete successfully. Fails fast on the first error.\n\n```go\nf1 := future.Async(func() (int, error) { return 1, nil })\nf2 := future.Async(func() (int, error) { return 2, nil })\nfAll := future.AllOf(f1, f2)\nvals, _ := fAll.Get() // [1, 2]\n```\n\n---\n\n### `AnyOf(fs ...*Future[T]) *Future[AnyResult[T]]`\n\nReturns the first successful result. If all fail, returns the first error.\n\n```go\nf1 := future.Async(func() (int, error) { return 0, fmt.Errorf(\"fail\") })\nf2 := future.Async(func() (int, error) { return 2, nil })\nres, _ := future.AnyOf(f1, f2).Get()\n// res.Index == 1, res.Val == 2\n```\n\n---\n\n### `Timeout(f *Future[T], d time.Duration) *Future[T]`\n\nWraps a future and fails with `ErrTimeout` if not resolved in time.\n\n```go\nf := future.Async(func() (int, error) {\n\ttime.Sleep(2 * time.Second)\n\treturn 42, nil\n})\nval, err := future.Timeout(f, time.Second).Get()\n// err == future.ErrTimeout\n```\n\n---\n\n### `Done(val T) *Future[T]`, `Done2(val T, err error)`\n\nCreate a completed Future.\n\n```go\nf := future.Done(\"value\")\nf2 := future.Done2(\"value\", nil)\n```\n\n---\n\n### `Subscribe(cb func(T, error))`\n\nRegisters a callback that runs when the Future is done.\n\n```go\nf := future.Async(func() (int, error) { return 1, nil })\nf.Subscribe(func(v int, err error) {\n\tfmt.Println(\"got:\", v)\n})\n```\n\n\u003e ⚠️ Callbacks execute **in the same goroutine** that completes the Future. Avoid blocking operations in the callback.\n\n---\n\n## ✅ Advantages\n\n* **Zero Locking:** Internals are implemented using atomic state machines, not `sync.Mutex`.\n* **Type Safe:** Full support for Go generics.\n* **No Goroutine Bloat:** Except `Async`, all operations are event-driven, avoiding extra goroutines.\n* **Composable:** Easily chainable, supports DAG-like workflows.\n\n---\n\n## 📊 Benchmark\n\n```text\ngoos: darwin\ngoarch: arm64\npkg: github.com/jizhuozhi/go-future\nBenchmark/Promise           3.05M\t    377 ns/op\nBenchmark/WaitGroup         2.88M\t    424 ns/op\nBenchmark/Channel           3.00M\t    399 ns/op\n```\n\n\u003e `Promise` is competitive with `sync.WaitGroup` and `channel` in terms of performance and offers much better composition semantics.\n\n---\n\n# 📦 DAG Execution Engine (Experimental)\n\nStarting from v0.1.4, `go-future` introduces a powerful **DAG (Directed Acyclic Graph) execution engine**, consisting of:\n\n* `dagcore`: A minimal, lock-free parallel DAG scheduler\n* `dagfunc`: A high-level builder that constructs DAGs using Go function signatures with type-based dependency resolution\n\nThis enables users to describe complex data flow graphs declaratively with automatic dependency wiring and parallel execution.\n\n## dagcore\n\n`dagcore` is the low-level DAG execution engine powering [`go-future`](https://github.com/jizhuozhi/go-future)'s structured concurrency and dataflow execution model. It provides a lock-free, dependency-driven scheduler for executing static DAGs (Directed Acyclic Graphs) in parallel.\n\n### ✨ Features\n\n* ⚡ **Lock-free execution** via atomic dependency counters\n* ⛓️ **Supports any static DAG with arbitrary fan-in/out structure**\n* 🔁 **Exactly-once execution**: each node runs exactly once after its dependencies complete\n* 🧠 **On-demand scheduling**: nodes are only triggered once all dependencies complete — goroutines are created only when the node is ready to run\n* ❌ **Fast failure support**: optional early cancellation on error (fail-fast mode)\n* ⏱️ **Context propagation**: full support for timeout and cancellation via `context.Context`\n* 🧩 **Composable foundation**: designed for embedding in higher-level DAG builders (e.g. `dagfunc`)\n* 📈 **Metrics \u0026 logging hooks**: supports per-node wrappers for observability (e.g. retry, timing, logging)\n\n---\n\n### 🚀 Example Usage\n\n```go\ndag := dagcore.NewDAG()\n\n// Define DAG structure\n_ = dag.AddInput(\"A\")\n_ = dag.AddNode(\"B\", []dagcore.NodeID{\"A\"}, func(ctx context.Context, deps map[dagcore.NodeID]any) (any, error) {\n    return deps[\"A\"].(int) + 2, nil\n})\n_ = dag.AddNode(\"C\", []dagcore.NodeID{\"A\"}, func(ctx context.Context, deps map[dagcore.NodeID]any) (any, error) {\n    return deps[\"A\"].(int) * 3, nil\n})\n\n// Verifies that the graph is complete and acyclic, \n// then locks the structure to make it immutable for repeated safe instantiations.\nif err := dag.Freeze(); err != nil {\n\treturn err\n}\n\n// Execute\ninst, _ := dag.Instantiate(map[dagcore.NodeID]any{\"A\": 10})\nres, _ := inst.Execute(context.Background())\nfmt.Println(\"B:\", res[\"B\"], \"C:\", res[\"C\"])\n```\n\n---\n\n### 🧠 Execution Model\n\nEach node in the DAG:\n\n* Declares its dependencies via `AddNode(id, deps, func)`\n* Executes only once **after all its inputs are ready**\n* Will not allocate any goroutine until scheduled — **on-demand execution**\n* May run in parallel with other ready nodes\n* Propagates failures down dependent nodes (fail-fast)\n\nInternally:\n\n* Uses atomic counters to track pending dependencies per node\n* Uses `future.Future` to propagate results, cancellation, and errors\n* Can be fully composed and integrated with the rest of `go-future`\n\n---\n\n### ⚙️ API Overview\n\n#### `dagcore.NewDAG() *DAG`\n\nCreates a new empty DAG instance.\n\n#### `(*DAG).AddInput(id NodeID) error`\n\nAdds a node that must be externally provided during execution.\n\n#### `(*DAG).AddNode(id NodeID, deps []NodeID, fn NodeFunc) error`\n\nAdds a computational node with declared dependencies.\n\n#### (*DAG).Freeze() error\n\n**Freezes the DAG topology.** Verifies that the graph is complete and acyclic, then locks the structure to make it immutable for repeated safe instantiations.\n\n```go\ndag := dagcore.NewDAG()\n// Add nodes...\n_ = dag.Freeze()\n```\n\n\u003e You must call Freeze() before Instantiate or Run. Once frozen, the DAG can be instantiated and executed multiple times in parallel.\n\n#### `(*DAG).Instantiate(inputs map[NodeID]any, wrappers ...NodeFuncWrapper) (*DAGInstance, error)`\n\nCreates a runtime instance of the DAG for execution.\n\n#### `(*DAGInstance).Run(ctx context.Context) (map[NodeID]any, error)`\n\nExecutes all nodes and returns the final results.\n\n#### `(*DAGInstance).RunAsync(ctx context.Context) *Future[map[NodeID]any]`\n\nRuns asynchronously and returns a future.\n\n---\n\n### 🔧 Advanced Features\n\n#### NodeFunc Wrapping\n\nUse `NodeFuncWrapper` to wrap node logic for tracing, logging, retries, etc:\n\n```go\ndag.Instantiate(inputs, func(n *dagcore.NodeInstance, fn dagcore.NodeFunc) dagcore.NodeFunc {\n    return func(ctx context.Context, deps map[dagcore.NodeID]any) (any, error) {\n        start := time.Now()\n        out, err := fn(ctx, deps)\n        log.Printf(\"node %s took %s\", n.ID(), time.Since(start))\n        return out, err\n    }\n})\n```\n\n#### Mermaid Graph Output\n\nConvert the DAG to a [Mermaid.js](https://mermaid-js.github.io/) compatible graph string:\n\n```go\nfmt.Println(dagcore.ToMermaid(instance))\n```\n\n---\n\n### 🧱 Designed for Composition\n\n`dagcore` is intended to be embedded in high-level tools:\n\n* [`dagfunc`](../dagfunc): type-safe DAG builder with Go function signature inference\n* Custom domain-specific orchestrators, AI pipelines, CI/CD workflows\n* Any static dependency graph evaluation with result propagation\n\n## dagfunc\n\n`dagfunc` is a high-level, type-safe DAG (Directed Acyclic Graph) builder built on top of [`dagcore`](../dagcore). It allows you to define and execute dependency graphs by simply wiring Go functions based on their parameter and return types.\n\n### 🚀 What It Solves\n\n`dagfunc` abstracts away manual DAG construction by:\n\n- Automatically inferring node dependencies from function signatures\n- Resolving dependency order at compile-time\n- Mapping types to results without needing manual wiring\n\nIdeal for:\n\n- AI agent pipelines\n- Asynchronous service orchestration\n- Task graph composition with clear dependency semantics\n\n---\n\n### ✅ Features\n\n- ✅ Type-based dependency inference\n- ✅ Fully integrated with `go-future` for parallel execution\n- ✅ Reusable functions with Go-style declarations\n- ✅ Built-in support for context propagation and error handling\n- ✅ Alias support to distinguish same-type dependencies\n\n---\n\n### 🔧 Installation\n\n```bash\ngo get github.com/jizhuozhi/go-future/dagfunc\n````\n\n---\n\n### ✨ Example\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/jizhuozhi/go-future/dagfunc\"\n)\n\nfunc main() {\n\ttype Input string\n\ttype TokenCount int\n\n\tb := dagfunc.New()\n\n\t// Step 1: Declare input\n\t_ = b.Provide(Input(\"\"))\n\n\t// Step 2: Register function\n\t_ = b.Use(func(ctx context.Context, text Input) (TokenCount, error) {\n\t\treturn TokenCount(len(text)), nil\n\t})\n\n\t// Step 3: Verifies that the graph is complete and acyclic, \n\t// then locks the structure to make it immutable for repeated safe instantiations.\n\tif err := b.Freeze(); err != nil {\n\t\tpanic(err)\n    }\n\t\n\t// Step 4: Run\n\tprog, _ := b.Compile([]any{Input(\"hello world\")})\n\tout, _ := prog.Run(context.Background())\n\tfmt.Println(out[TokenCount(0)]) // Output: 11\n}\n```\n\n---\n\n### 🧠 Type-Based Wiring\n\n`dagfunc` determines node dependencies using **parameter types** and result types:\n\n* Each function must accept `context.Context` as the first argument\n* Inputs and outputs must use unique Go types or **aliases**\n* The DAG will automatically determine execution order\n\n\u003e ⚠️ If two inputs/outputs are of the same type (e.g., multiple `string` values), use `type alias` to disambiguate.\n\n#### With type alias\n\n```go\ntype UserID string\ntype Greeting string\n\nb.Provide(UserID(\"\"))\nb.Use(func(ctx context.Context, uid UserID) (Greeting, error) {\n\treturn Greeting(\"Hello, \" + string(uid)), nil\n})\nb.Use(func(ctx context.Context, g Greeting) (string, error) {\n\treturn string(g), nil\n})\n```\n\n---\n\n### 🧰 API Overview\n\n#### `dagfunc.New() *Builder`\n\nCreates a new DAG builder.\n\n#### `(*Builder).Provide(val any) error`\n\nDeclares a root node with known value.\n\n#### `(*Builder).Use(fn any) error`\n\nRegisters a function as a DAG node. Must match:\n\n```go\nfunc(ctx context.Context, A, B, ...) (X, Y, ..., error)\n```\n\n#### `(*Builder).Compile(inputs []any) (*Program, error)`\n\nBuilds a DAG using the provided inputs.\n\n#### `(*Program).Run(ctx context.Context) (map[any]any, error)`\n\nExecutes the DAG. Outputs are keyed by result types with typed zero.\n\n#### `(*Program).RunAsync(ctx context.Context) *future.Future[map[any]any]`\n\nExecutes the DAG. Return a future with outputs are keyed by result types with typed zero.\n\n#### `(*Program).Get(any) (any, error)`\n\nGets the result value for a specific type.\n\n#### Error propagation\n\n* DAG execution will **fail fast** by default\n* Downstream nodes will not be executed if inputs fail\n* You can customize error behavior using `dagcore`\n\n---\n\n### 🧩 Relationship to dagcore\n\n| Layer     | Role                                      |\n| --------- | ----------------------------------------- |\n| dagfunc   | High-level: Build DAGs from Go functions  |\n| dagcore   | Low-level: Execute DAGs with scheduling   |\n| go-future | Runtime: Power async execution via Future |\n\n---\n\n### 💡 Use Cases\n\n* LLM / Agent planning pipelines\n* Microservice DAG invocation\n* Declarative orchestration of business logic\n* Build systems / task runners\n\n---\n\n### 📌 Notes\n\n* Outputs are retrieved by Go types (typed zero), not labels\n* Type aliasing is required for disambiguation\n* All dependencies must be resolvable at compile-time\n\n## 🔐 License\n\nApache-2.0 license by [jizhuozhi](https://github.com/jizhuozhi)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjizhuozhi%2Fgo-future","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjizhuozhi%2Fgo-future","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjizhuozhi%2Fgo-future/lists"}