Core API¶
The asyncresult artifact contains the core types and utilities for modeling asynchronous operations.
Types¶
The library provides a sealed class hierarchy to represent the different states:
NotStarted- Idle state before an operation begins. Useful for user-initiated actions that haven't started yet.Loading- The operation is in-flight.Success<T>- The operation completed successfully, containing the result value.Error- The operation failed. Can optionally contain aThrowableand typed metadata for context.Incomplete- A marker interface implemented by bothNotStartedandLoading, useful for bundling them in exhaustivewhenstatements.
val result: AsyncResult<User> = Loading
Transforming values¶
Mapping¶
Transform the success value while preserving the result state:
val userName: AsyncResult<String> = userResult.mapSuccess { it.name }
Transform errors:
val enrichedResult = result.mapError { error ->
error.withMetadata(NetworkFailure)
}
Folding¶
Generate a value from any state:
val message = result.fold(
ifNotStarted = { "Waiting" },
ifLoading = { "Loading..." },
ifSuccess = { "Hello ${it.name}" },
ifError = { "Error: ${it.throwable?.message}" },
)
FlatMap and chaining¶
Chain operations that return AsyncResult:
val profile: AsyncResult<Profile> = userResult.flatMap { user ->
fetchProfile(user.id)
}
Use andThen for explicit chaining:
val result = fetchUser().andThen { user ->
fetchPermissions(user.id)
}
Filtering and casting¶
Filter values or convert to errors:
val admin: AsyncResult<User> = userResult.filterOrError { it.isAdmin }
val typed: AsyncResult<Admin> = result.castOrError<Admin>()
Handle nullable values:
val nonNull: AsyncResult<User> = nullableResult.orError()
Getting values¶
Extract the underlying value in different ways:
val value: User? = result.getOrNull()
val value: User = result.getOrDefault(defaultUser)
val value: User = result.getOrElse { fallbackUser }
val value: User = result.getOrThrow() // throws if not Success
// For collections
val items: List<Item> = listResult.getOrEmpty()
Extract error information:
val error: Error? = result.errorOrNull()
val throwable: Throwable? = result.throwableOrNull()
val metadata: MyError? = result.errorWithMetadataOrNull<MyError>()
Side effects¶
Run code based on the current state:
result
.onNotStarted { showPlaceholder() }
.onLoading { showSpinner() }
.onSuccess { user -> render(user) }
.onError { showError(it) }
.onErrorWithMetadata<R, NetworkError> { throwable, metadata ->
showNetworkError(metadata)
}
Unwrapping (Rust-style)¶
For when you're certain about the state and want to extract values directly:
val user: User = result.unwrap() // throws UnwrapException if not Success
val error: Error = result.unwrapError()
val throwable: Throwable = result.unwrapThrowable()
val metadata: MyError = result.unwrapMetadata<MyError>()
With custom error messages:
val user = result.expect { "User should be loaded by now" }
val error = result.expectError { "Expected failure" }
Combining results¶
Zipping¶
Combine multiple results into one:
val combined = zip(
{ userResult },
{ permissionsResult }
) { user, permissions ->
UserWithPermissions(user, permissions)
}
Or using the zipWith extension:
val combined = userResult.zipWith(permissionsResult) { user, permissions ->
UserWithPermissions(user, permissions)
}
Supports up to 4 results.
Spreading¶
Split a result containing a Pair or Triple:
val (userResult, settingsResult) = pairResult.spread()
Working with collections¶
Utilities for handling multiple results:
val results: List<AsyncResult<*>> = listOf(result1, result2, result3)
val errors: List<Error> = results.getAllErrors()
val throwables: List<Throwable> = results.getAllThrowables()
val isAnyLoading: Boolean = results.anyLoading()
val isAnyIncomplete: Boolean = results.anyIncomplete()
Standalone functions:
val hasError = anyError(result1, result2, result3)
val hasLoading = anyLoading(result1, result2, result3)
val errors = errorsFrom(result1, result2, result3)
Flow helpers¶
Extensions for Flow<AsyncResult<T>>:
flowOf(result)
.onLoading { showSpinner() }
.onSuccess { render(it) }
.onError { showError(it) }
Extract values from flows:
val value: User = flow.getOrThrow()
val value: User? = flow.getOrNull()
val value: User = flow.getOrElse { fallbackUser }
Filtering¶
Skip loading states entirely:
flow.skipWhileLoading()
.collect { result ->
// Only receives NotStarted, Success, or Error
}
// Alias
flow.filterNotLoading()
Caching¶
Cache the latest success value and emit it during reloads:
flow.cacheLatestSuccess()
.collect { result ->
// During reload: shows cached Success instead of Loading
}
This is useful for "stale-while-revalidate" patterns where you want to show existing data while fetching updates.
Timeout¶
Convert slow operations to errors:
flow.timeoutToError(5.seconds) {
TimeoutException("Request timed out")
}
If no Success or Error is emitted within the timeout, an Error with the provided throwable is emitted.
Retry¶
Automatically retry on errors:
flow.retryOnError(
maxRetries = 3,
delay = 1.seconds,
predicate = { error ->
error.throwable is IOException // Only retry network errors
}
)
The flow will restart from the beginning on each retry attempt.