async — a Coroutine Builder in Kotlin that launches a coroutine and returns a Deferred<T> object for obtaining the result of an asynchronous operation. async allows executing multiple tasks in parallel and aggregating their results via await(). According to JetBrains guide, 2024, async is the preferred way to organize parallel computations in coroutines.
Key Takeaways
async — is a CoroutineScope extension function that creates a coroutine returning a result. Unlike launch, async returns Deferred<T> — an object representing a future value. A coroutine launched via async executes concurrently with other coroutines, allowing computations to be parallelized.
async is used when the result of an asynchronous operation is needed for further processing: loading data from multiple sources, batch processing collections, parallel requests to various APIs. Deferred is created immediately, and the result becomes available after the coroutine completes.
import kotlinx.coroutines.*
suspend fun String.delayedValue(): String {
delay(1000L)
return this
}
fun main() = runBlocking {
val deferred: Deferred<String> = async { "Hello".delayedValue() }
println(deferred.await())
}
The async signature is similar to launch: the same CoroutineContext, CoroutineStart parameters, and a suspend block. The difference is in the return type — Deferred<T> instead of Job, where T is the result type returned by the last expression of the block.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — an interface inheriting Job and adding await(), getCompleted(), getCompletionExceptionOrNull() methods. await() is a suspend function and can only be called inside a coroutine or another suspend function. getCompleted() is a non-blocking method that throws an exception if the coroutine has not yet completed.
The main advantage of async is the ability to run tasks in parallel. Instead of waiting for each operation sequentially (total time = sum of times), async launches coroutines simultaneously, reducing total time to the duration of the longest operation.
A typical scenario — loading a user profile, their settings, and order history with three parallel async requests. After all three complete, the results are combined into a single data model.
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { api.getProfile(userId) }
val settings = async { api.getSettings(userId) }
val orders = async { api.getRecentOrders(userId) }
Dashboard(
profile = profile.await(),
settings = settings.await(),
orders = orders.await()
)
}
The coroutineScope function launches child coroutines and waits for their completion before returning the result. The entire block executes in parallel, not sequentially.
Deferred supports several methods for working with the result. In addition to await(), there are methods for checking status, getting a completed value, and aggregating multiple Deferreds through Kotlin Coroutines utility functions.
| Method | Description | Suspend? |
|---|---|---|
| await() | Suspends the coroutine until the result is obtained | Yes |
| getCompleted() | Returns the result if the coroutine has completed (otherwise Exception) | No |
| getCompletionExceptionOrNull() | Returns the exception on error or null | No |
| isCompleted | Checks whether the coroutine has completed | No |
For aggregating a list of Deferreds, awaitAll() is used — a function that suspends execution until all Deferreds in the collection complete. If at least one completed with an error, awaitAll() throws the exception.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
The choice between async and launch depends on whether the coroutine result is needed. launch returns Job and is suitable for fire-and-forget operations, async returns Deferred and is used for tasks that return data. In terms of syntax and parameters, the builders are identical.
Important rule: never use async for fire-and-forget. If the coroutine result is not needed, use launch. async creates overhead for creating Deferred, which is not justified in such scenarios.
In Android, async is used for parallel operations inside ViewModel and UseCases. Typical scenarios: loading profile and news feed simultaneously, fetching weather from multiple weather services, batch data synchronization with the server.
class HomeViewModel : ViewModel() {
fun loadHomeScreen() {
viewModelScope.launch {
val userData = async(Dispatchers.IO) { repository.getUserData() }
val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
val notifications = async(Dispatchers.IO) { repository.getNotifications() }
val state = HomeState(
user = userData.await(),
news = newsFeed.await(),
unread = notifications.await()
)
_uiState.update { state }
}
}
}
It is recommended to wrap a group of async calls in coroutineScope or supervisorScope for error isolation. supervisorScope allows the remaining async coroutines to continue even if one fails — useful for non-critical operations.
Frequently Asked Questions
It can, but it is pointless. async without await() launches a coroutine but its result is lost. If the result is not needed, use launch — it does not create a Deferred and is more efficient in terms of memory and performance.
An exception inside async is stored in Deferred. Calling await() throws this exception. To check without throwing, use getCompletionExceptionOrNull(). When using coroutineScope, an error cancels all child coroutines.
awaitAll() — an extension function for Iterable<Deferred<T>> that suspends the coroutine until all Deferreds complete. Returns a list of results. If at least one Deferred fails, it throws the exception and cancels the rest.
Yes. async is a CoroutineScope extension function tied to the scope. GlobalScope.async creates a coroutine without lifecycle binding — it can run indefinitely. GlobalScope is not recommended in Android due to the risk of leaks.
The number is dictated by the dispatcher: Dispatchers.IO supports up to 64 concurrent threads, Dispatchers.Default — up to the number of CPU cores. For bulk operations, use limited parallelism via Semaphore or mapNotNull with awaitAll.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also