@zio.dev/zio-blocks 0.0.51 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +6 -0
- package/guides/getting-started-with-mux.md +0 -112
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +395 -1
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +131 -70
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +200 -559
- package/package.json +1 -1
- package/reference/async.md +1379 -531
- package/reference/chunk.md +3 -3
- package/reference/codegen/index.md +1 -1
- package/reference/combinators.md +4 -4
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +6 -49
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +2 -2
- package/reference/docs.md +2 -2
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +1 -0
- package/reference/endpoint/index.md +9 -89
- package/reference/endpoint/path-codec.md +12 -24
- package/reference/endpoint/route-pattern.md +4 -6
- package/reference/endpoint/segment-codec.md +19 -32
- package/reference/html.md +313 -9
- package/reference/htmx/index.md +4 -52
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +3 -1
- package/reference/http-model/model.md +107 -71
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +6 -3
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +128 -11
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/index.md +1 -1
- package/reference/resource-management/resource.md +2 -98
- package/reference/resource-management/scope.md +1 -209
- package/reference/resource-management/wire.md +4 -50
- package/reference/ringbuffer/advanced.mdx +1 -1
- package/reference/ringbuffer/index.mdx +3 -3
- package/reference/ringbuffer/mpmc.mdx +38 -4
- package/reference/ringbuffer/mpsc.mdx +36 -4
- package/reference/ringbuffer/spmc.mdx +1 -1
- package/reference/ringbuffer/spsc.mdx +87 -15
- package/reference/schema/allows.md +0 -96
- package/reference/schema/binding.md +2 -2
- package/reference/schema/built-in-codecs/avro.md +2 -2
- package/reference/schema/built-in-codecs/bson.md +50 -20
- package/reference/schema/built-in-codecs/csv.md +2 -2
- package/reference/schema/built-in-codecs/index.md +3 -3
- package/reference/schema/built-in-codecs/json/index.md +2 -2
- package/reference/schema/built-in-codecs/json/json.md +1 -0
- package/reference/schema/built-in-codecs/messagepack.md +3 -3
- package/reference/schema/built-in-codecs/thrift.md +2 -2
- package/reference/schema/built-in-codecs/toon.md +3 -3
- package/reference/schema/built-in-codecs/yaml.md +2 -2
- package/reference/schema/codec.md +11 -11
- package/reference/schema/dynamic-optic.md +48 -3
- package/reference/schema/dynamic-schema.md +3 -3
- package/reference/schema/index.md +2 -0
- package/reference/schema/path-interpolator.md +2 -0
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/schema/schema-evolution/as.md +4 -4
- package/reference/schema/schema-evolution/into.md +2 -2
- package/reference/schema/schema-expr.md +2 -2
- package/reference/schema/schema-search.md +263 -0
- package/reference/schema/schema.md +10 -2
- package/reference/schema/type-class-derivation.md +1 -1
- package/reference/smithy.md +502 -3
- package/reference/sql/db-codec-deriver.md +3 -3
- package/reference/sql/db-codec.md +22 -22
- package/reference/sql/db-con.md +4 -4
- package/reference/sql/db-connection.md +1 -1
- package/reference/sql/db-param.md +1 -1
- package/reference/sql/db-result-reader.md +4 -2
- package/reference/sql/db-tx.md +46 -14
- package/reference/sql/ddl.md +1 -1
- package/reference/sql/frag.md +44 -10
- package/reference/sql/index.md +7 -7
- package/reference/sql/repo.md +15 -15
- package/reference/sql/sql-dialect.md +1 -1
- package/reference/sql/sql-logger.md +1 -1
- package/reference/sql/sql-name-mapper.md +3 -3
- package/reference/sql/table-metadata.md +3 -3
- package/reference/sql/table.md +10 -10
- package/reference/sql/transactor-zio.md +1 -1
- package/reference/sql/transactor.md +21 -11
- package/reference/sql-zio.md +2 -2
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
- package/reference/streams/{sink.md → core/sink.md} +331 -353
- package/reference/streams/{stream.md → core/stream.md} +919 -209
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +140 -67
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/{writer.md → primitives/writer.md} +254 -98
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +0 -64
- package/sidebars.js +365 -185
- package/undocumented-report.md +528 -270
- package/reference/config.md +0 -158
- package/reference/streams/concurrent-operators.md +0 -106
- package/reference/streams/reader.md +0 -1284
- package/reference/streams/scala-2-compatibility.md +0 -55
- package/reference/streams/zero-boxing.md +0 -275
- package/reference/telemetry.md +0 -693
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: async-getting-started
|
|
3
|
+
title: "Getting Started with Async"
|
|
4
|
+
description: "Learn to create, compose, and run async effects in Scala with the zero-allocation Async[A] type."
|
|
5
|
+
keywords:
|
|
6
|
+
- "Asynchronous Effects"
|
|
7
|
+
- "Zero Allocation"
|
|
8
|
+
- "Direct Style"
|
|
9
|
+
- "Error Handling"
|
|
10
|
+
- "Async"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
import Tabs from '@theme/Tabs';
|
|
14
|
+
import TabItem from '@theme/TabItem';
|
|
15
|
+
|
|
16
|
+
Welcome! This tutorial introduces `Async[A]`, a zero-allocation effect type from ZIO Blocks that unifies ready values, failures, and genuinely suspended computations under a single type and combinator set. If you know basic Scala syntax and have a sense of what an effect type is, you have everything you need to follow along.
|
|
17
|
+
|
|
18
|
+
## 1. Introduction
|
|
19
|
+
|
|
20
|
+
By the end of this tutorial, you will be able to:
|
|
21
|
+
|
|
22
|
+
- Create ready-value effects with `Async.succeed` and transform them with `Async#map` and `Async#block`.
|
|
23
|
+
- Handle errors with `Async.fail`, `Async#catchAll`, and `Async#either`.
|
|
24
|
+
- Write sequential async code in direct style using `Async.async { … .await … }`.
|
|
25
|
+
- Bridge throw-based code and callback-based APIs with `Async.attempt` and `Async.promise` / `Completer`.
|
|
26
|
+
- Fork background computations with `Async#start` and cancel them with `Async#cancel`.
|
|
27
|
+
|
|
28
|
+
To add the async module to your project, include this dependency in your `build.sbt`:
|
|
29
|
+
|
|
30
|
+
```scala
|
|
31
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-async" % "0.0.56"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then bring the full DSL into scope at the top of each file you use it from:
|
|
35
|
+
|
|
36
|
+
```scala
|
|
37
|
+
import zio.blocks.async._
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
We recommend reading from top to bottom — each section builds directly on the one before it.
|
|
41
|
+
|
|
42
|
+
## 2. Background: What Is `Async[A]`?
|
|
43
|
+
|
|
44
|
+
`Async[A]` was designed to solve a specific problem: code often juggles three different kinds of values — results that are already available, computations that need to wait for I/O or a callback, and failures. Treating these differently in different places creates friction. `Async[A]` is one abstraction that covers all three.
|
|
45
|
+
|
|
46
|
+
The design's most important property is its **happy-path allocation budget: zero**. When you chain `Async.succeed`, `Async#map`, and another `map` call together, every step is a plain function call. No wrapper objects accumulate on the heap. Only a computation that truly suspends — waiting for a callback, a timer, or a thread — leaves a pending object behind. This is the zero-allocation promise: you pay for suspension only when you actually suspend.
|
|
47
|
+
|
|
48
|
+
We will explore `Async[A]` through an order-processing scenario: looking up a user via a callback API, parsing an order ID, checking stock availability in the background, and recovering gracefully from any failure — one concept at a time.
|
|
49
|
+
|
|
50
|
+
## 3. Ready Values: `Async.succeed`, `map`, and `block`
|
|
51
|
+
|
|
52
|
+
The simplest async computation is one that already has its answer. `Async.succeed(value)` wraps an available value into an `Async[A]` so it can take part in async chains without allocating anything. Once you have an `Async`, you transform it with `Async#map` and drive it to its final result with `Async#block`.
|
|
53
|
+
|
|
54
|
+
Here we wrap the integer `42`, double it with `map`, then extract the result:
|
|
55
|
+
|
|
56
|
+
```scala
|
|
57
|
+
import zio.blocks.async._
|
|
58
|
+
|
|
59
|
+
val result: Int = Async.succeed(42).map(_ * 2).block
|
|
60
|
+
println(s"Ready mapped: $result")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Expected output:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
Ready mapped: 84
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `Async.succeed(42)` wraps `42` as a ready-value `Async[Int]` with zero allocation.
|
|
70
|
+
- Calling `map` with `_ * 2` applies the function; because the input is already ready, the entire chain is just a function call — no suspension object is created.
|
|
71
|
+
- The `block` call is the eager driver — it polls the async until it completes and returns the final value. It is safe to call on the JVM; on Scala.js it throws if the computation is still suspended.
|
|
72
|
+
|
|
73
|
+
Try changing `42` to a different number and watch the output change accordingly.
|
|
74
|
+
|
|
75
|
+
## 4. Error Handling: `Async.fail`, `catchAll`, and `either`
|
|
76
|
+
|
|
77
|
+
Not every computation succeeds. `Async.fail(throwable)` creates a failed async that short-circuits all downstream `map` calls without invoking their functions. `Async#catchAll` recovers by applying a function that returns a new `Async`.
|
|
78
|
+
|
|
79
|
+
Here we create a failed computation and recover from it with `catchAll`:
|
|
80
|
+
|
|
81
|
+
```scala
|
|
82
|
+
import zio.blocks.async._
|
|
83
|
+
|
|
84
|
+
val recovered: String = Async.fail(new Exception("oops"))
|
|
85
|
+
.catchAll(_ => Async.succeed("default"))
|
|
86
|
+
.block
|
|
87
|
+
println(s"Recovered: $recovered")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Expected output:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Recovered: default
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `Async.fail(new Exception("oops"))` creates a computation that carries the exception as its failure.
|
|
97
|
+
- Calling `catchAll` with a recovery function intercepts the failure; the lambda ignores the specific error and returns a ready-value replacement.
|
|
98
|
+
- The `block` call drives the recovered chain to its result, `"default"`.
|
|
99
|
+
|
|
100
|
+
Sometimes you want to observe both the success and failure branches as plain data rather than handle them immediately. `Async#either` reifies both outcomes as `Either[Throwable, A]` so you can pattern-match on them:
|
|
101
|
+
|
|
102
|
+
```scala
|
|
103
|
+
import zio.blocks.async._
|
|
104
|
+
|
|
105
|
+
val observed: Either[Throwable, Int] = Async.fail(new Exception("error"))
|
|
106
|
+
.either
|
|
107
|
+
.block
|
|
108
|
+
println(s"Observed: $observed")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Expected output:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
Observed: Left(java.lang.Exception: error)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- Calling `either` wraps the failure in `Left`; a successful result would appear in `Right`, turning the async's outcome into an ordinary Scala value.
|
|
118
|
+
- The `block` call materialises that `Either` so the learner can inspect it.
|
|
119
|
+
|
|
120
|
+
## 5. Direct Style: `Async.async` and `await`
|
|
121
|
+
|
|
122
|
+
Writing nested `Async#flatMap` chains is precise but becomes hard to read when many steps depend on each other. `Async.async { … }` lets you write that same sequencing in direct style: inside the block, call `Async#await` on any `Async` to extract its value and bind it to a local variable, as if you were writing straight-line code. The compiler rewrites every `await` call into a `flatMap` chain at compile time, so the runtime behaviour is identical.
|
|
123
|
+
|
|
124
|
+
Here we compose a user name and an order ID without a single explicit `flatMap`:
|
|
125
|
+
|
|
126
|
+
```scala
|
|
127
|
+
import zio.blocks.async._
|
|
128
|
+
|
|
129
|
+
val summary: String = Async.async {
|
|
130
|
+
val user = Async.succeed("Ada").await
|
|
131
|
+
val order = Async.succeed(9001).await
|
|
132
|
+
s"${user}'s order ${order}"
|
|
133
|
+
}.block
|
|
134
|
+
println(s"Summary: $summary")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Expected output:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
Summary: Ada's order 9001
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- `Async.async { … }` opens a macro-powered block; the entire expression produces an `Async[String]`.
|
|
144
|
+
- Calling `await` on `Async.succeed("Ada")` extracts `"Ada"` and binds it to `user`; this is not a blocking call — the macro rewrites it into a `flatMap` continuation.
|
|
145
|
+
- Calling `await` on `Async.succeed(9001)` similarly binds the order number to `order`.
|
|
146
|
+
- The final string expression becomes the block's result value.
|
|
147
|
+
- The `block` call drives the whole composed async to completion.
|
|
148
|
+
|
|
149
|
+
:::caution[`await` Is Only Valid Inside `Async.async { … }`]
|
|
150
|
+
The `await` method is enforced by the compiler to be used only inside an `Async.async { … }` block. On Scala 3 the inline expansion fails with:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
".await may only be used directly inside an Async.async { ... } block."
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
On Scala 2, the `@compileTimeOnly` annotation fires at the same point. Try deleting the `Async.async { … }` wrapper — the compiler will tell you immediately.
|
|
157
|
+
:::
|
|
158
|
+
|
|
159
|
+
## 6. Bridging Exceptions: `Async.attempt`
|
|
160
|
+
|
|
161
|
+
Scala code often signals failure by throwing exceptions rather than returning error values. `Async.attempt(body)` evaluates a block that may throw and captures any exception as an async failure, turning it into a value that `catchAll` can recover. A block that succeeds produces a ready-value `Async`; one that throws produces a failed `Async`.
|
|
162
|
+
|
|
163
|
+
The example parses a well-formed string and then a malformed one:
|
|
164
|
+
|
|
165
|
+
```scala
|
|
166
|
+
import zio.blocks.async._
|
|
167
|
+
|
|
168
|
+
val good: Int = Async.attempt("42".toInt).block
|
|
169
|
+
println(s"Parsed: $good")
|
|
170
|
+
|
|
171
|
+
val bad: Either[Throwable, Int] = Async.attempt("oops".toInt).either.block
|
|
172
|
+
println(s"Failed parse: $bad")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Expected output:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
Parsed: 42
|
|
179
|
+
Failed parse: Left(java.lang.NumberFormatException: For input string: "oops")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- `Async.attempt("42".toInt)` evaluates `"42".toInt`; because it succeeds, the result `42` becomes a ready-value `Async[Int]`.
|
|
183
|
+
- `Async.attempt("oops".toInt)` evaluates `"oops".toInt`; the `NumberFormatException` is caught and becomes a failed `Async`.
|
|
184
|
+
- Chaining `either` and `block` drives the failed async and reifies its outcome as `Left(…)`.
|
|
185
|
+
|
|
186
|
+
## 7. Callback Bridging: `Async.promise` and `Completer`
|
|
187
|
+
|
|
188
|
+
Many real-world APIs — database drivers, network libraries, timers — signal completion by calling a callback rather than returning a value. `Async.promise` lets you lift these APIs into `Async` without rewriting them. It suspends the computation and provides a `Completer[A]` — a thread-safe, one-shot handle — that you can pass to the callback. Calling `completer.succeed(value)` from any thread resolves the async and wakes up any awaiter.
|
|
189
|
+
|
|
190
|
+
The example starts a background thread that completes the promise after a short delay:
|
|
191
|
+
|
|
192
|
+
<Tabs groupId="scala-version" defaultValue="scala2">
|
|
193
|
+
<TabItem value="scala2" label="Scala 2">
|
|
194
|
+
|
|
195
|
+
```scala
|
|
196
|
+
import zio.blocks.async._
|
|
197
|
+
|
|
198
|
+
val result: String = Async.promise[String] { c =>
|
|
199
|
+
new Thread {
|
|
200
|
+
override def run(): Unit = {
|
|
201
|
+
Thread.sleep(10)
|
|
202
|
+
c.succeed("hello from callback")
|
|
203
|
+
}
|
|
204
|
+
}.start()
|
|
205
|
+
}.block
|
|
206
|
+
println(s"Promise resolved: $result")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
</TabItem>
|
|
210
|
+
<TabItem value="scala3" label="Scala 3">
|
|
211
|
+
|
|
212
|
+
```scala
|
|
213
|
+
import zio.blocks.async._
|
|
214
|
+
|
|
215
|
+
val result: String = Async.promise[String] {
|
|
216
|
+
val c = summon[Completer[String]]
|
|
217
|
+
new Thread {
|
|
218
|
+
override def run(): Unit = {
|
|
219
|
+
Thread.sleep(10)
|
|
220
|
+
c.succeed("hello from callback")
|
|
221
|
+
}
|
|
222
|
+
}.start()
|
|
223
|
+
}.block
|
|
224
|
+
println(s"Promise resolved: $result")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
</TabItem>
|
|
228
|
+
</Tabs>
|
|
229
|
+
|
|
230
|
+
Expected output:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
Promise resolved: hello from callback
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- `Async.promise[String] { … }` opens the promise body; on Scala 2 the `Completer[String]` arrives as an explicit parameter `c`; on Scala 3 it arrives as a context function argument retrieved with `summon[Completer[String]]`.
|
|
237
|
+
- We capture the completer in `c` so it can be referenced from the Thread's `run()` method — implicits and givens do not propagate across thread boundaries, so we capture explicitly.
|
|
238
|
+
- Calling `c.succeed("hello from callback")` completes the promise; the first call wins and subsequent calls are no-ops.
|
|
239
|
+
- The `block` call waits until the completer fires and returns the resolved value.
|
|
240
|
+
|
|
241
|
+
## 8. Forking and Cancellation: `start` and `Async.Running`
|
|
242
|
+
|
|
243
|
+
By default, async chains run eagerly on the calling thread until the first suspension or completion. To drive a computation on a **background worker** (a separate JVM thread or a Scala.js microtask queue entry) instead, call `Async#start` on any `Async`. This returns an `Async.Running[A]` handle — itself an `Async[A]` — representing the in-flight computation. You can join it by calling `block` on the handle, or stop it early with `cancel`.
|
|
244
|
+
|
|
245
|
+
The first block forks a computation and joins it:
|
|
246
|
+
|
|
247
|
+
```scala
|
|
248
|
+
import zio.blocks.async._
|
|
249
|
+
|
|
250
|
+
val running: Async.Running[Int] = Async.succeed(42)
|
|
251
|
+
.map { x => println(s"Running in background: $x"); x * 2 }
|
|
252
|
+
.start
|
|
253
|
+
val joined: Int = running.block
|
|
254
|
+
println(s"Joined: $joined")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Expected output:
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
Running in background: 42
|
|
261
|
+
Joined: 84
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- Calling `start` forks the entire chain — `Async.succeed(42)` plus the `map` — onto a background worker; the calling thread continues immediately.
|
|
265
|
+
- `running.block` blocks the calling thread until the background computation finishes and returns the result `84`.
|
|
266
|
+
- The background computation prints its message before returning the value, so that line appears first.
|
|
267
|
+
|
|
268
|
+
The companion method `Async.start` forks a plain expression. The following block demonstrates cancellation:
|
|
269
|
+
|
|
270
|
+
```scala
|
|
271
|
+
import zio.blocks.async._
|
|
272
|
+
|
|
273
|
+
val running2: Async.Running[Int] = Async.start { Thread.sleep(100); 99 }
|
|
274
|
+
running2.cancel()
|
|
275
|
+
println("Cancelled running2")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Expected output:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
Cancelled running2
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- `Async.start { Thread.sleep(100); 99 }` forks the block onto a background worker; the call returns the `Running` handle immediately.
|
|
285
|
+
- Calling `cancel` stops the driver loop; if cancellation linearises before the computation finishes, the result is never published to any awaiter. The call is idempotent.
|
|
286
|
+
|
|
287
|
+
## 9. Putting It Together
|
|
288
|
+
|
|
289
|
+
Let's combine all six concepts into a single order-processing pipeline. The program bridges a callback-based user-lookup API with `Async.promise`, safely parses an order ID with `Async.attempt`, runs a stock check in the background with `start`, sequences everything in direct style inside `Async.async`, and recovers from any failure with `catchAll`:
|
|
290
|
+
|
|
291
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CompleteExample.scala"
|
|
292
|
+
package zio.blocks.async.gettingstarted
|
|
293
|
+
|
|
294
|
+
import zio.blocks.async._
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Complete Example: Order Processing Pipeline
|
|
298
|
+
*
|
|
299
|
+
* Combines all core `Async` concepts in a single order-processing scenario:
|
|
300
|
+
* - `Async.promise` + `Completer` to bridge a callback-based user-lookup API
|
|
301
|
+
* - `Async.attempt` to safely parse a potentially malformed order ID
|
|
302
|
+
* - `.start` + `Async.Running` to check stock availability in the background
|
|
303
|
+
* - `Async.async { .await }` for direct-style sequential composition
|
|
304
|
+
* - `.catchAll` to recover from any failure in the pipeline
|
|
305
|
+
*
|
|
306
|
+
* Run with:
|
|
307
|
+
* {{{
|
|
308
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
|
|
309
|
+
* }}}
|
|
310
|
+
*/
|
|
311
|
+
object CompleteExample {
|
|
312
|
+
def main(args: Array[String]): Unit = {
|
|
313
|
+
|
|
314
|
+
// Step 1: Bridge a callback-based user-lookup API with promise + Completer
|
|
315
|
+
val userLookup: Async[String] = Async.promise[String] {
|
|
316
|
+
val c = summon[Completer[String]]
|
|
317
|
+
new Thread {
|
|
318
|
+
override def run(): Unit = {
|
|
319
|
+
Thread.sleep(10)
|
|
320
|
+
c.succeed("Ada")
|
|
321
|
+
}
|
|
322
|
+
}.start()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Step 2: Parse an order ID that might be malformed
|
|
326
|
+
val orderId: Async[Int] = Async.attempt("9001".toInt)
|
|
327
|
+
|
|
328
|
+
// Step 3: Start a stock-availability check in the background
|
|
329
|
+
val stockCheck: Async.Running[Boolean] = Async.succeed(true).map { v => Thread.sleep(5); v }.start
|
|
330
|
+
|
|
331
|
+
// Step 4: Compose all steps in direct style and recover from any failure
|
|
332
|
+
val report: String = Async.async {
|
|
333
|
+
val user = userLookup.await
|
|
334
|
+
val id = orderId.await
|
|
335
|
+
val inStock = stockCheck.await
|
|
336
|
+
s"Order $id for $user: ${if (inStock) "in stock" else "out of stock"}"
|
|
337
|
+
}.catchAll { err =>
|
|
338
|
+
Async.succeed(s"Order failed: ${err.getMessage}")
|
|
339
|
+
}.block
|
|
340
|
+
|
|
341
|
+
println(report)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## 10. Running the Examples
|
|
347
|
+
|
|
348
|
+
Clone the repository and move into its root directory:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
352
|
+
cd zio-blocks
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Each concept's standalone example is shown below. Expand a section to see the source and the command to run it.
|
|
356
|
+
|
|
357
|
+
<details>
|
|
358
|
+
<summary><strong>Concept 1: Ready Values</strong></summary>
|
|
359
|
+
|
|
360
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ReadyValuesExample.scala" showLineNumbers
|
|
361
|
+
package zio.blocks.async.gettingstarted
|
|
362
|
+
|
|
363
|
+
import zio.blocks.async._
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Section 1: Ready Values
|
|
367
|
+
*
|
|
368
|
+
* Demonstrates how to wrap an already-available value into an `Async` with
|
|
369
|
+
* `Async.succeed`, transform it using `.map`, and drive it to a result with
|
|
370
|
+
* `.block`.
|
|
371
|
+
*
|
|
372
|
+
* Run with:
|
|
373
|
+
* {{{
|
|
374
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.ReadyValuesExample"
|
|
375
|
+
* }}}
|
|
376
|
+
*/
|
|
377
|
+
object ReadyValuesExample {
|
|
378
|
+
def main(args: Array[String]): Unit = {
|
|
379
|
+
val result: Int = Async.succeed(42).map(_ * 2).block
|
|
380
|
+
println(s"Ready mapped: $result")
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Run it with:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.ReadyValuesExample"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
</details>
|
|
392
|
+
|
|
393
|
+
<details>
|
|
394
|
+
<summary><strong>Concept 2: Error Handling</strong></summary>
|
|
395
|
+
|
|
396
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ErrorHandlingExample.scala" showLineNumbers
|
|
397
|
+
package zio.blocks.async.gettingstarted
|
|
398
|
+
|
|
399
|
+
import zio.blocks.async._
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Section 2: Error Handling
|
|
403
|
+
*
|
|
404
|
+
* Demonstrates how to create failed async computations with `Async.fail`,
|
|
405
|
+
* recover from failures using `.catchAll`, and reify both outcomes as
|
|
406
|
+
* `Either[Throwable, A]` using `.either`.
|
|
407
|
+
*
|
|
408
|
+
* Run with:
|
|
409
|
+
* {{{
|
|
410
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.ErrorHandlingExample"
|
|
411
|
+
* }}}
|
|
412
|
+
*/
|
|
413
|
+
object ErrorHandlingExample {
|
|
414
|
+
def main(args: Array[String]): Unit = {
|
|
415
|
+
val recovered: String = Async
|
|
416
|
+
.fail(new Exception("oops"))
|
|
417
|
+
.catchAll(_ => Async.succeed("default"))
|
|
418
|
+
.block
|
|
419
|
+
println(s"Recovered: $recovered")
|
|
420
|
+
|
|
421
|
+
val observed: Either[Throwable, Int] = Async.fail(new Exception("error")).either.block
|
|
422
|
+
println(s"Observed: $observed")
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Run it with:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.ErrorHandlingExample"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
</details>
|
|
434
|
+
|
|
435
|
+
<details>
|
|
436
|
+
<summary><strong>Concept 3: Direct Style</strong></summary>
|
|
437
|
+
|
|
438
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/DirectStyleExample.scala" showLineNumbers
|
|
439
|
+
package zio.blocks.async.gettingstarted
|
|
440
|
+
|
|
441
|
+
import zio.blocks.async._
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Section 3: Direct Style
|
|
445
|
+
*
|
|
446
|
+
* Demonstrates how to write sequential async code in direct style using
|
|
447
|
+
* `Async.async { ... }` and `.await`. The compiler rewrites `.await` calls into
|
|
448
|
+
* `flatMap` chains, so the code reads like straight-line imperative code
|
|
449
|
+
* without explicit callback nesting.
|
|
450
|
+
*
|
|
451
|
+
* Run with:
|
|
452
|
+
* {{{
|
|
453
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.DirectStyleExample"
|
|
454
|
+
* }}}
|
|
455
|
+
*/
|
|
456
|
+
object DirectStyleExample {
|
|
457
|
+
def main(args: Array[String]): Unit = {
|
|
458
|
+
val summary: String = Async.async {
|
|
459
|
+
val user = Async.succeed("Ada").await
|
|
460
|
+
val order = Async.succeed(9001).await
|
|
461
|
+
s"${user}'s order ${order}"
|
|
462
|
+
}.block
|
|
463
|
+
println(s"Summary: $summary")
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Run it with:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.DirectStyleExample"
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
</details>
|
|
475
|
+
|
|
476
|
+
<details>
|
|
477
|
+
<summary><strong>Concept 4: Bridging Exceptions</strong></summary>
|
|
478
|
+
|
|
479
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/AttemptExample.scala" showLineNumbers
|
|
480
|
+
package zio.blocks.async.gettingstarted
|
|
481
|
+
|
|
482
|
+
import zio.blocks.async._
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Section 4: Bridging Exceptions
|
|
486
|
+
*
|
|
487
|
+
* Demonstrates how `Async.attempt` evaluates a block that may throw and
|
|
488
|
+
* captures any exception as an async failure rather than propagating it as a
|
|
489
|
+
* JVM exception. A successful evaluation produces a ready-value `Async`; a
|
|
490
|
+
* thrown exception produces a failed `Async` recoverable with `.catchAll`.
|
|
491
|
+
*
|
|
492
|
+
* Run with:
|
|
493
|
+
* {{{
|
|
494
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.AttemptExample"
|
|
495
|
+
* }}}
|
|
496
|
+
*/
|
|
497
|
+
object AttemptExample {
|
|
498
|
+
def main(args: Array[String]): Unit = {
|
|
499
|
+
val good: Int = Async.attempt("42".toInt).block
|
|
500
|
+
println(s"Parsed: $good")
|
|
501
|
+
|
|
502
|
+
val bad: Either[Throwable, Int] = Async.attempt("oops".toInt).either.block
|
|
503
|
+
println(s"Failed parse: $bad")
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Run it with:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.AttemptExample"
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
</details>
|
|
515
|
+
|
|
516
|
+
<details>
|
|
517
|
+
<summary><strong>Concept 5: Callback Bridging</strong></summary>
|
|
518
|
+
|
|
519
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CallbackBridgeExample.scala" showLineNumbers
|
|
520
|
+
package zio.blocks.async.gettingstarted
|
|
521
|
+
|
|
522
|
+
import zio.blocks.async._
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Section 5: Callback Bridging
|
|
526
|
+
*
|
|
527
|
+
* Demonstrates how to lift a callback-based API into an `Async` using
|
|
528
|
+
* `Async.promise` and a `Completer`. The body of `Async.promise` is a Scala 3
|
|
529
|
+
* context function: `summon[Completer[A]]` retrieves the completer, which can
|
|
530
|
+
* then be captured and called from any thread.
|
|
531
|
+
*
|
|
532
|
+
* Run with:
|
|
533
|
+
* {{{
|
|
534
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.CallbackBridgeExample"
|
|
535
|
+
* }}}
|
|
536
|
+
*/
|
|
537
|
+
object CallbackBridgeExample {
|
|
538
|
+
def main(args: Array[String]): Unit = {
|
|
539
|
+
val result: String = Async
|
|
540
|
+
.promise[String] {
|
|
541
|
+
val c = summon[Completer[String]]
|
|
542
|
+
new Thread {
|
|
543
|
+
override def run(): Unit = {
|
|
544
|
+
Thread.sleep(10)
|
|
545
|
+
c.succeed("hello from callback")
|
|
546
|
+
}
|
|
547
|
+
}.start()
|
|
548
|
+
}
|
|
549
|
+
.block
|
|
550
|
+
println(s"Promise resolved: $result")
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Run it with:
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.CallbackBridgeExample"
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
</details>
|
|
562
|
+
|
|
563
|
+
<details>
|
|
564
|
+
<summary><strong>Concept 6: Forking and Cancellation</strong></summary>
|
|
565
|
+
|
|
566
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ForkingExample.scala" showLineNumbers
|
|
567
|
+
package zio.blocks.async.gettingstarted
|
|
568
|
+
|
|
569
|
+
import zio.blocks.async._
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Section 6: Forking and Cancellation
|
|
573
|
+
*
|
|
574
|
+
* Demonstrates how to run a computation on a background worker with `.start`,
|
|
575
|
+
* which returns an `Async.Running[A]` handle. The handle can be joined with
|
|
576
|
+
* `.block` (blocks the caller until the computation completes) or cancelled
|
|
577
|
+
* with `.cancel()` (stops the driver loop before the result is published).
|
|
578
|
+
*
|
|
579
|
+
* Run with:
|
|
580
|
+
* {{{
|
|
581
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.ForkingExample"
|
|
582
|
+
* }}}
|
|
583
|
+
*/
|
|
584
|
+
object ForkingExample {
|
|
585
|
+
def main(args: Array[String]): Unit = {
|
|
586
|
+
// Fork and join
|
|
587
|
+
val running: Async.Running[Int] = Async.succeed(42).map { x => println(s"Running in background: $x"); x * 2 }.start
|
|
588
|
+
val joined: Int = running.block
|
|
589
|
+
println(s"Joined: $joined")
|
|
590
|
+
|
|
591
|
+
// Fork and cancel
|
|
592
|
+
val running2: Async.Running[Int] = Async.start { Thread.sleep(100); 99 }
|
|
593
|
+
running2.cancel()
|
|
594
|
+
println("Cancelled running2")
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Run it with:
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.ForkingExample"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
</details>
|
|
606
|
+
|
|
607
|
+
<details>
|
|
608
|
+
<summary><strong>Complete Example: Order Processing Pipeline</strong></summary>
|
|
609
|
+
|
|
610
|
+
```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CompleteExample.scala" showLineNumbers
|
|
611
|
+
package zio.blocks.async.gettingstarted
|
|
612
|
+
|
|
613
|
+
import zio.blocks.async._
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Complete Example: Order Processing Pipeline
|
|
617
|
+
*
|
|
618
|
+
* Combines all core `Async` concepts in a single order-processing scenario:
|
|
619
|
+
* - `Async.promise` + `Completer` to bridge a callback-based user-lookup API
|
|
620
|
+
* - `Async.attempt` to safely parse a potentially malformed order ID
|
|
621
|
+
* - `.start` + `Async.Running` to check stock availability in the background
|
|
622
|
+
* - `Async.async { .await }` for direct-style sequential composition
|
|
623
|
+
* - `.catchAll` to recover from any failure in the pipeline
|
|
624
|
+
*
|
|
625
|
+
* Run with:
|
|
626
|
+
* {{{
|
|
627
|
+
* sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
|
|
628
|
+
* }}}
|
|
629
|
+
*/
|
|
630
|
+
object CompleteExample {
|
|
631
|
+
def main(args: Array[String]): Unit = {
|
|
632
|
+
|
|
633
|
+
// Step 1: Bridge a callback-based user-lookup API with promise + Completer
|
|
634
|
+
val userLookup: Async[String] = Async.promise[String] {
|
|
635
|
+
val c = summon[Completer[String]]
|
|
636
|
+
new Thread {
|
|
637
|
+
override def run(): Unit = {
|
|
638
|
+
Thread.sleep(10)
|
|
639
|
+
c.succeed("Ada")
|
|
640
|
+
}
|
|
641
|
+
}.start()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Step 2: Parse an order ID that might be malformed
|
|
645
|
+
val orderId: Async[Int] = Async.attempt("9001".toInt)
|
|
646
|
+
|
|
647
|
+
// Step 3: Start a stock-availability check in the background
|
|
648
|
+
val stockCheck: Async.Running[Boolean] = Async.succeed(true).map { v => Thread.sleep(5); v }.start
|
|
649
|
+
|
|
650
|
+
// Step 4: Compose all steps in direct style and recover from any failure
|
|
651
|
+
val report: String = Async.async {
|
|
652
|
+
val user = userLookup.await
|
|
653
|
+
val id = orderId.await
|
|
654
|
+
val inStock = stockCheck.await
|
|
655
|
+
s"Order $id for $user: ${if (inStock) "in stock" else "out of stock"}"
|
|
656
|
+
}.catchAll { err =>
|
|
657
|
+
Async.succeed(s"Order failed: ${err.getMessage}")
|
|
658
|
+
}.block
|
|
659
|
+
|
|
660
|
+
println(report)
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Run it with:
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
</details>
|
|
672
|
+
|
|
673
|
+
## 11. What You've Learned
|
|
674
|
+
|
|
675
|
+
By completing this tutorial, you can now:
|
|
676
|
+
|
|
677
|
+
- Create ready-value effects with `Async.succeed`, transform them with `map`, and drive them to a result with `block`.
|
|
678
|
+
- Handle failures with `Async.fail`, recover them with `catchAll`, and observe both outcomes as data with `either`.
|
|
679
|
+
- Write sequential async pipelines in direct style using `Async.async { … .await … }` — the compiler threads the `flatMap` calls for you.
|
|
680
|
+
- Lift throw-based Scala code safely into the async error channel with `Async.attempt`.
|
|
681
|
+
- Bridge a callback-based API with `Async.promise` and `Completer`, fork background work with `start`, and hold a cancellable `Async.Running` handle.
|
|
682
|
+
|
|
683
|
+
## 12. Where to Go Next
|
|
684
|
+
|
|
685
|
+
The [Async reference page](../reference/async.md) documents every method and combinator with full signatures — it is the natural next stop once you are comfortable with the basics in this tutorial.
|
|
686
|
+
|
|
687
|
+
If your application manages resources that need deterministic cleanup — database connections, file handles, or connection pools — read the [Compile-Time Resource Safety with Scope](./compile-time-resource-safety-with-scope.md) tutorial, which shows how to tie resource lifetimes to lexical scopes and compose them without try/finally boilerplate.
|
|
@@ -994,3 +994,9 @@ You now understand Scope's core concepts:
|
|
|
994
994
|
- **Thread ownership** — JVM enforcement of structured concurrency.
|
|
995
995
|
|
|
996
996
|
For complete API documentation, see the [Scope Reference](../reference/resource-management/scope.md).
|
|
997
|
+
|
|
998
|
+
## See Also
|
|
999
|
+
|
|
1000
|
+
- [Telemetry Reference](../reference/telemetry/index.md) — `TracerProvider`, `LoggerProvider`, and `MeterProvider` are `AutoCloseable`, so the ownership rules here carry over to their lifetimes
|
|
1001
|
+
- [Telemetry Guide](./telemetry-guide.md) — Provider startup and shutdown ordering, done with plain `AutoCloseable` shutdown rather than `Scope`
|
|
1002
|
+
- [Async Reference](../reference/async.md) — `Async.Running` extends `AutoCloseable` and integrates with `scala.util.Using` for scoped cancellation; the same resource-ownership mental model used by Scope applies to in-flight async computations
|