wandres.dev
CONCURRENCIA · async/await y actores

async/await y tareas

Escribe código asíncrono que se lee como síncrono. async, await, Task y la concurrencia estructurada que hace legible lo que antes eran callbacks anidados.

⏱ 15 min

Descargar datos, leer un archivo, esperar una animación: todo eso lleva tiempo, y bloquear la app mientras ocurre es inaceptable. async/await te deja escribir ese código asíncrono como si fuera una secuencia normal, sin la “pirámide de la perdición” de los callbacks.

🎯 Al terminar esta lección sabrás
  • Funciones async y la palabra await.
  • Lanzar trabajo asíncrono con Task.
  • Concurrencia estructurada: async let.
  • El modificador .task en SwiftUI.

async y await

Una función que tarda se marca async. Al llamarla, pones await: ahí tu código “se pausa” hasta que el resultado esté listo, sin bloquear el hilo:

func obtenerUsuario() async -> String {
    // simula una espera (una red, un disco…)
    try? await Task.sleep(for: .seconds(1))
    return "William"
}

// llamarla:
let nombre = await obtenerUsuario()
print("Hola, \(nombre)")

El código tras el await se ejecuta cuando el resultado llega, pero se lee de arriba abajo como algo secuencial. Ahí está la magia.

Adiós a la pirámide de callbacks

Antes, encadenar tres operaciones asíncronas era un anidamiento infernal: un callback dentro de otro dentro de otro, con el manejo de errores desperdigado. Con async/await, esas tres operaciones son tres líneas seguidas con await, y los errores se manejan con el do/try/catch normal (Nivel 1). El código asíncrono por fin se lee como el síncrono: en orden, plano y con el flujo de errores unificado. Es uno de los mayores saltos de legibilidad en la historia de Swift.

Task: entrar en el mundo async

Desde código síncrono (como la acción de un botón), lanzas trabajo asíncrono con Task:

Button("Cargar") {
    Task {
        let nombre = await obtenerUsuario()
        print(nombre)
    }
}

Task crea una unidad de trabajo concurrente. También puedes cancelarla y comprobar cancelación, importante para no malgastar recursos.

Concurrencia estructurada: async let

Si necesitas varias cosas a la vez, async let las lanza en paralelo y esperas a ambas juntas:

func cargarPerfil() async -> (String, Int) {
    async let nombre = obtenerNombre()   // arrancan
    async let edad = obtenerEdad()        // ambas a la vez

    return (await nombre, await edad)     // esperas los dos resultados
}

Sin async let, harías await de una y luego de otra: en serie, más lento. Con él, corren en paralelo y el total es el tiempo de la más lenta, no la suma.

.task en SwiftUI

Para cargar datos cuando aparece una vista, el modificador .task es perfecto: lanza trabajo async atado al ciclo de vida de la vista (se cancela solo si la vista desaparece):

struct PerfilView: View {
    @State private var nombre = "Cargando…"

    var body: some View {
        Text(nombre)
            .task {
                nombre = await obtenerUsuario()
            }
    }
}
💡
.task, no .onAppear + Task

.task es la forma idiomática de cargar datos al mostrar una vista: es async de forma nativa y cancela el trabajo automáticamente si el usuario navega fuera antes de que termine. Evita el viejo patrón de .onAppear { Task { … } }, que no gestiona la cancelación. Detalle pequeño, gran diferencia en apps que hacen muchas peticiones.

⚔️ Piensa en asíncrono
  1. Escribe una función async que espere un segundo y devuelva un texto.
  2. Llámala desde un botón con Task { await … }.
  3. Lanza dos funciones async en paralelo con async let y espera ambas.
  4. Carga datos al mostrar una vista con el modificador .task.