Coming from Raw Coroutines¶
If you're using coroutineScope { async { } } for parallel execution, this guide shows how KAP simplifies your code while keeping all the structured concurrency guarantees you rely on.
KAP is built ON coroutines¶
KAP doesn't replace kotlinx.coroutines — it uses it internally. .evalGraph() creates a coroutineScope. .with uses async. All structured concurrency rules still apply:
- Parent cancels → all children cancel
- One child fails → siblings cancel (unless
.settled()) CancellationExceptionis never caughtCoroutineContextpropagates to all branches
Simple parallel: async/await → kap + .with¶
6 lines → 4 lines. No shuttle variables. Swap two .with lines? Compile error.
Phased execution: nested coroutineScope → .then¶
// Where does phase 1 end? Read every line to find out.
val result = coroutineScope {
val dA = async { fetchA() }
val dB = async { fetchB() }
val a = dA.await()
val b = dB.await()
val validated = validate(a, b) // invisible barrier
val dC = async { fetchC() }
val dD = async { fetchD() }
Result(a, b, validated, dC.await(), dD.await())
}
@KapTypeSafe
data class Result(val a: A, val b: B, val validated: Validated, val c: C, val d: D)
val result = kap(::Result)
.with { a from fetchA() } // ┐ phase 1
.with { b from fetchB() } // ┘
.then { validated from validate() } // ── explicit barrier
.with { c from fetchC() } // ┐ phase 2
.with { d from fetchD() } // ┘
.evalGraph()
Bounded concurrency: Semaphore → traverse(concurrency)¶
Timeout with fallback: withTimeoutOrNull → .timeout¶
Parallel fallback: sequential → timeoutRace¶
Error recovery: try/catch → .recover¶
Retry: manual loop → Schedule¶
var result: String? = null
var lastException: Exception? = null
repeat(3) { attempt ->
try {
result = fetchUser()
return@repeat
} catch (e: Exception) {
if (e is CancellationException) throw e
lastException = e
delay(100L * (attempt + 1)) // linear backoff, hardcoded
}
}
result ?: throw lastException!!
Resource cleanup: try/finally → bracket¶
Partial failure: supervisorScope → .settled()¶
// Builder function: handles the Result wrapper explicitly
fun buildDashboard(user: Result<String>, cart: String): Dashboard =
Dashboard(user.getOrDefault("anonymous"), cart)
val result = kap(::buildDashboard)
.with(BuildDashboardKap.user from settled { fetchUserMayFail() }) // .settled() → Result<String>
.with { cart from fetchCart() } // normal String
.evalGraph()
// fetchUserMayFail() fails → Result.failure → buildDashboard uses "anonymous"
// fetchCart() is NOT cancelled
Cheat sheet¶
| Raw Coroutines | KAP |
|---|---|
coroutineScope { async { } } |
kap(::T).with { }.evalGraph() |
async { }.await() |
.with { } |
| suspend call between phases | .then { } |
nested coroutineScope |
.andThen { ctx -> } |
Semaphore + async |
traverse(concurrency) |
withTimeoutOrNull |
.timeout(d) { default } |
try/catch |
.recover { } |
try/finally |
bracket(acquire, use, release) |
supervisorScope |
.settled() |
select { } |
race() / raceN() |
| manual retry loop | retry(Schedule) |