async — es un constructor de corrutinas en Kotlin que lanza una corrutina y devuelve un objeto Deferred<T> para obtener el resultado de una operación asíncrona. async permite ejecutar múltiples tareas en paralelo y agregar sus resultados mediante await(). Según la guía de JetBrains, 2024, async es la forma preferida de organizar cálculos paralelos en corrutinas.
Puntos Clave
async — es una función de extensión de CoroutineScope que crea una corrutina devolviendo un resultado. A diferencia de launch, async devuelve Deferred<T> — un objeto que representa un valor futuro. Una corrutina lanzada mediante async se ejecuta concurrentemente con otras corrutinas, permitiendo paralelizar cálculos.
async se usa cuando el resultado de una operación asíncrona es necesario para su posterior procesamiento: carga de datos de múltiples fuentes, procesamiento por lotes de colecciones, solicitudes paralelas a varias API. Deferred se crea inmediatamente y el resultado está disponible después de que la corrutina se complete.
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())
}
La firma de async es similar a launch: los mismos parámetros CoroutineContext, CoroutineStart y un bloque suspend. La diferencia está en el tipo de retorno — Deferred<T> en lugar de Job, donde T es el tipo de resultado devuelto por la última expresión del bloque.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — una interfaz que hereda Job y agrega los métodos await(), getCompleted(), getCompletionExceptionOrNull(). await() es una función suspend y solo puede llamarse dentro de una corrutina u otra función suspend. getCompleted() es un método no bloqueante que lanza una excepción si la corrutina aún no se ha completado.
La principal ventaja de async es la capacidad de ejecutar tareas en paralelo. En lugar de esperar cada operación secuencialmente (tiempo total = suma de tiempos), async lanza corrutinas simultáneamente, reduciendo el tiempo total a la duración de la operación más larga.
Un escenario típico — cargar el perfil del usuario, sus ajustes y el historial de pedidos con tres solicitudes async paralelas. Después de que las tres se completen, los resultados se combinan en un único modelo de datos.
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()
)
}
La función coroutineScope lanza corrutinas hijas y espera su finalización antes de devolver el resultado. Todo el bloque se ejecuta en paralelo, no secuencialmente.
Deferred admite varios métodos para trabajar con el resultado. Además de await(), hay métodos para verificar el estado, obtener un valor completado y agregar varios Deferred mediante las funciones de utilidad de Kotlin Coroutines.
| Método | Descripción | ¿Suspend? |
|---|---|---|
| await() | Suspende la corrutina hasta obtener el resultado | Sí |
| getCompleted() | Devuelve el resultado si la corrutina se completó (si no, Exception) | No |
| getCompletionExceptionOrNull() | Devuelve la excepción en caso de error o null | No |
| isCompleted | Comprueba si la corrutina se ha completado | No |
Para agregar una lista de Deferred, se usa awaitAll() — una función que suspende la ejecución hasta que todos los Deferred de la colección se completen. Si al menos uno se completó con error, awaitAll() lanza la excepción.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
La elección entre async y launch depende de si se necesita el resultado de la corrutina. launch devuelve Job y es adecuado para operaciones de fire-and-forget, async devuelve Deferred y se usa para tareas que devuelven datos. En cuanto a sintaxis y parámetros, los constructores son idénticos.
Regla importante: nunca uses async para fire-and-forget. Si no se necesita el resultado de la corrutina, usa launch. async crea una sobrecarga por crear Deferred, lo que no está justificado en tales escenarios.
En Android, async se usa para operaciones paralelas dentro de ViewModel y UseCases. Escenarios típicos: cargar perfil y feed de noticias simultáneamente, obtener el clima de varios servicios meteorológicos, sincronización por lotes de datos con el servidor.
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 }
}
}
}
Se recomienda envolver un grupo de async en coroutineScope o supervisorScope para aislar errores. supervisorScope permite que las demás corrutinas async continúen incluso si una falla — útil para operaciones no críticas.
Preguntas Frecuentes
Se puede, pero no tiene sentido. async sin await() lanza una corrutina pero su resultado se pierde. Si no se necesita el resultado, usa launch — no crea un Deferred y es más eficiente en memoria y rendimiento.
Una excepción dentro de async se almacena en Deferred. Llamar a await() lanza esta excepción. Para comprobar sin lanzar, usa getCompletionExceptionOrNull(). Al usar coroutineScope, un error cancela todas las corrutinas hijas.
awaitAll() — una función de extensión para Iterable<Deferred<T>> que suspende la corrutina hasta que todos los Deferred se completen. Devuelve una lista de resultados. Si al menos un Deferred falla, lanza la excepción y cancela el resto.
Sí. async es una función de extensión de CoroutineScope vinculada al ámbito. GlobalScope.async crea una corrutina sin vinculación al ciclo de vida — puede ejecutarse indefinidamente. GlobalScope no se recomienda en Android debido al riesgo de fugas.
El número lo dicta el despachador: Dispatchers.IO admite hasta 64 subprocesos simultáneos, Dispatchers.Default — hasta el número de núcleos de CPU. Para operaciones masivas, usa paralelismo limitado mediante Semaphore o mapNotNull con awaitAll.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también