O que é async em Kotlin?

O async é um coroutine builder que inicia uma coroutine e retorna um Deferred<T> – uma promessa de que um resultado vai estar disponivel no futuro. Para obter o valor, você chama await().

Enquanto launch e “dispara e esquece”, async e “dispara e pega o resultado depois”. E a ferramenta certa quando você precisa executar tarefas em paralelo e combinar os resultados.

Imagine que você esta num restaurante e pede um prato principal é uma sobremesa. Em vez de esperar o prato principal ficar pronto para só entao pedir a sobremesa, você faz os dois pedidos de uma vez. A cozinha prepara ambos em paralelo e você recebe tudo mais rápido. E exatamente assim que o async funciona: dispara várias tarefas simultaneamente e coleta os resultados quando estao prontos.

Exemplo básico

import kotlinx.coroutines.*

suspend fun buscarPreco(): Double {
    delay(1000)
    return 49.90
}

suspend fun buscarEstoque(): Int {
    delay(1000)
    return 150
}

fun main() = runBlocking {
    val inicio = System.currentTimeMillis()

    val preco = async { buscarPreco() }
    val estoque = async { buscarEstoque() }

    println("Preco: R$ ${preco.await()}")
    println("Estoque: ${estoque.await()} unidades")
    println("Tempo: ${System.currentTimeMillis() - inicio}ms")
    // Tempo: ~1000ms (paralelo, nao 2000ms)
}

As duas chamadas rodam ao mesmo tempo. Se fossem sequenciais, levaria 2 segundos. Com async, leva apenas 1.

Deferred e como um Future

O Deferred funciona de maneira parecida com Future ou Promise de outras linguagens. Ele representa um valor que ainda esta sendo calculado.

val resultado: Deferred<String> = async {
    delay(500)
    "Pronto!"
}

// Faz outras coisas enquanto espera...
println(resultado.await()) // Bloqueia so aqui se ainda nao terminou

Cuidado com async desnecessário

Um erro comum e usar async seguido de await imediatamente, o que não da nenhum ganho:

// Ruim -- e o mesmo que chamar a funcao diretamente
val resultado = async { buscarPreco() }.await()

// Bom -- executa em paralelo com outra tarefa
val a = async { buscarPreco() }
val b = async { buscarEstoque() }
println("${a.await()} - ${b.await()}")

Tratamento de erros

Se uma exceção acontece dentro do async, ela e propagada quando você chama await():

val resultado = async {
    throw RuntimeException("Deu ruim!")
}

try {
    resultado.await()
} catch (e: Exception) {
    println("Erro: ${e.message}")
}

Carregamento paralelo de dados de API

Um cenário muito comum e carregar dados de múltiplas fontes simultaneamente para montar uma tela ou resposta:

import kotlinx.coroutines.*

data class PerfilUsuario(val nome: String, val pedidos: List<String>, val pontos: Int)

suspend fun buscarNome(userId: String): String {
    delay(800) // simula chamada de rede
    return "Maria Silva"
}

suspend fun buscarPedidos(userId: String): List<String> {
    delay(1200)
    return listOf("Pedido #101", "Pedido #102", "Pedido #103")
}

suspend fun buscarPontos(userId: String): Int {
    delay(600)
    return 4500
}

suspend fun carregarPerfil(userId: String): PerfilUsuario = coroutineScope {
    val nome = async { buscarNome(userId) }
    val pedidos = async { buscarPedidos(userId) }
    val pontos = async { buscarPontos(userId) }

    PerfilUsuario(
        nome = nome.await(),
        pedidos = pedidos.await(),
        pontos = pontos.await()
    )
    // Tempo total: ~1200ms (o mais lento), nao 2600ms
}

Async com tratamento robusto de falhas

Quando uma das tarefas paralelas pode falhar, use supervisorScope para evitar que todas as outras sejam canceladas:

import kotlinx.coroutines.*

suspend fun carregarDadosComFallback() = supervisorScope {
    val principal = async {
        // pode falhar
        buscarPreco()
    }

    val reserva = async {
        delay(500)
        39.90 // preco de fallback
    }

    try {
        principal.await()
    } catch (e: Exception) {
        println("Fonte principal falhou, usando reserva")
        reserva.await()
    }
}

Processamento em lote com async

Quando você precisa processar uma lista de itens em paralelo:

import kotlinx.coroutines.*

suspend fun processarItem(id: Int): String {
    delay(500)
    return "Resultado do item $id"
}

fun main() = runBlocking {
    val ids = (1..10).toList()

    val resultados = ids.map { id ->
        async { processarItem(id) }
    }.awaitAll()

    resultados.forEach { println(it) }
    // Todos os 10 itens processados em ~500ms, nao 5000ms
}

Casos de Uso no Mundo Real

  • Telas de aplicativos: carregar dados do usuário, notificacoes e recomendacoes em paralelo para montar uma tela inicial rapidamente.
  • Agregacao de microsservicos: um servico de backend chama vários outros servicos simultaneamente e combina as respostas em um único objeto.
  • Processamento de imagens: redimensionar, aplicar filtros e gerar thumbnails de uma imagem ao mesmo tempo.
  • Validacao paralela: verificar disponibilidade de username, validar email e consultar CEP simultaneamente durante um cadastro.

Boas Praticas

  • Sempre use coroutineScope ou supervisorScope para estruturar suas chamadas async, garantindo que falhas sejam tratadas corretamente.
  • Use awaitAll() quando tiver uma colecao de Deferred, em vez de chamar await() individualmente em cada um.
  • Escolha o Dispatcher adequado: Dispatchers.IO para operações de rede ou disco, Dispatchers.Default para processamento pesado de CPU.
  • Evite async dentro de GlobalScope, pois você perde o controle do ciclo de vida da coroutine.
  • Considere usar Flow quando os dados chegam como um stream continuo em vez de um resultado único.

Erros Comuns

  • async + await imediato: async { tarefa() }.await() e equivalente a chamar tarefa() diretamente. O async só faz sentido quando você dispara múltiplas tarefas antes de chamar await.
  • Ignorar exceções: se você nunca chama await() em um Deferred que falhou, a exceção pode ser engolida silenciosamente, dificultando a depuração.
  • Criar coroutines demais: disparar milhares de async sem limitar a concorrencia pode sobrecarregar recursos. Use Semaphore ou processe em lotes.
  • Esquecer a concorrencia estruturada: usar GlobalScope.async em vez de coroutineScope pode causar vazamento de coroutines se o escopo pai for cancelado.

Perguntas Frequentes

Qual a diferenca entre async e launch? launch retorna um Job e e usado quando você não precisa de um resultado (fire-and-forget). async retorna um Deferred<T> e e usado quando você precisa do resultado da computacao.

Posso usar async sem await? Tecnicamente sim, mas a coroutine vai executar sem que você colete o resultado. Se ocorrer uma exceção, ela pode ser perdida. Sempre chame await() ou use awaitAll().

async roda em outra thread? Depende do Dispatcher. Por padrão, async herda o dispatcher do escopo pai. Se você usar async(Dispatchers.IO), ele executara em uma thread do pool de IO.

Quando devo usar async vs Channel? Use async para tarefas que produzem um resultado único. Use Channel para comunicação continua entre coroutines, onde dados sao enviados e recebidos ao longo do tempo.