Back home

Swift Concurrency Series 02|Problemas resueltos por async/await

Es hacer que el sistema asíncrono recupere las condiciones para el mantenimiento a largo plazo.

Si solo miras la superficie, async/await parece resolver principalmente tres problemas:

  • demasiadas devoluciones de llamadas
  • Anidación demasiado profunda
  • El código es demasiado feo.

Pero después de usarlo en el proyecto durante un período de tiempo, descubrirá que son solo apariencias. Lo que realmente resuelve es: ** Los negocios asincrónicos finalmente pueden organizarse, razonarse y reutilizarse como los negocios normales. **

Este es un cambio de mantenibilidad de ingeniería.

1. Lo primero que resuelve es: los procesos de negocio ya no son secuestrados por métodos de escritura

Muchas empresas asincrónicas en sí mismas no son complicadas. Lo complicado es que el antiguo método de escritura lo complica.

Por ejemplo, un proceso real pero común:

  1. Extraer información del usuario
  2. Verifique el estado de la membresía
  3. Extraiga el contenido recomendado en la página de inicio.
  4. Si uno de los pasos falla, regrese a la pantalla completa.
  5. Estado de la última página actualizada

Este habría sido un proceso secuencial muy claro.

Pero en la era de la finalización, a menudo tenemos que lidiar primero con estos “problemas de escritura”:

  • ¿Dónde está escrita la sucursal exitosa?
  • ¿Dónde está escrita la rama fallida?
  • ¿Dónde está escrito el cambio de hilo principal?
  • ¿Qué capa es responsable del retorno anticipado? -¿Qué capa se encarga de unificar los errores de las bombas?

Entonces la complejidad ya no viene del negocio, sino de la propia expresión. async/await El beneficio más directo es eliminar esta capa adicional de ruido y devolver la complejidad empresarial al propio negocio.

2. Vuelve a dejar clara la “dependencia”

Muchos procesos asincrónicos son fuertemente dependientes.

Por ejemplo:

  • Sólo después de obtener el estado de inicio de sesión podrá obtener la información del usuario.
  • Sólo después de obtener la información del usuario podrás decidir qué módulos cargar en la página de inicio.
  • Sólo después de obtener la configuración experimental puedes decidir la ruta de visualización de la página.

Por supuesto, la finalización también puede expresar estas relaciones, pero el problema es que a menudo oculta dependencias en capas de anidamiento. El código se puede ejecutar, pero la cadena de dependencia ya no está clara.

Uno de los valores fundamentales de async/await es devolver esta relación de “quién depende de quién” a un flujo de control lineal:

let session = try await authService.loadSession()
let user = try await userService.loadUser(session: session)
let modules = try await homeService.loadModules(for: user)

El valor de este código no es sólo que se vea bien, sino que se pueda ver inmediatamente:

  • Relación secuencial
  • punto de quiebre
  • ¿Qué paso puede estar mal?

Esto afectará directamente su confianza al modificar los requisitos más adelante, porque finalmente podrá determinar dónde está la sección que está moviendo en todo el enlace.

3. Mejora significativamente la propagación de errores

En el antiguo modelo asincrónico, el manejo de errores era el más frágil.

Debido a que cada nivel de finalización puede fallar, esto a menudo termina en esta situación:

  • Un conjunto de manejo para fallas en solicitudes de primer nivel.
  • Otro conjunto de tratamientos para el fallo de la segunda capa.
  • Si el tercer nivel falla, prueba con otro.
  • Algunos lugares se lo tragaron mal.
  • Tocar brindis en algunos lugares.
  • Algunos lugares solo registran

Descubrirá que el verdadero problema es que la ruta del error está dividida como el proceso principal.

async/await al menos proporciona un modelo unificado de propagación de errores:

  • Desechar esta capa
  • Sigue lanzando hacia arriba.
  • Responsabilidad unificada de niveles superiores.

En otras palabras, no decide por el equipo “cómo tratar los errores”, pero al menos deja claro “cómo fluyen los errores”. Esto es especialmente importante para los límites comerciales.

4. Expone explícitamente el punto de pausa

Una situación común es ignorar el significado más importante de await: Es un recordatorio.

Es revelador:

  • Posible pausa aquí
  • El contexto aquí puede variar.
  • El código posterior a esto no debería estar exactamente en el mismo entorno de forma predeterminada.

Esto es clave para el pensamiento concurrente.

Porque el verdadero peligro del código asincrónico es que después de llamarlo, a menudo piensas inconscientemente:

  • La página actual todavía está ahí.
  • El estado actual no ha cambiado.
  • El objeto actual permanece intacto.

Y await al menos marca claramente “este es el límite” gramaticalmente. Esto le obligará a empezar a pensar seriamente en la validez del estado, en lugar de tratar la función asincrónica como una llamada a función ordinaria.

5. Hace que sea más fácil ingresar al diseño principal en lugar de la lógica del complemento.

En el pasado, muchos códigos también podían cancelar, pero la cancelación generalmente era como una función de recuperación:

  • Guardar un token por separado
  • Recuerda cancelar cuando se destruya la página.
  • Detener manualmente solicitudes antiguas en algunos escenarios

¿Se puede hacer? Por supuesto que puedes. Pero el problema es que la cancelación en el modelo antiguo a menudo no es parte del proceso principal, sino como un parche externo.

En Swift Concurrency, las tareas y la cancelación finalmente se ven de manera más natural como un conjunto de cosas. Esto obliga a plantearse varias preguntas clave antes:

  • ¿A quién pertenece esta tarea?
  • ¿Debería detenerse cuando se abandona la página?
  • Nuevas tareas están por llegar, ¿las tareas antiguas deberían dejar de ser válidas?

No ayuda automáticamente a que la cancelación sea correcta, pero pone de relieve el hecho de que la cancelación debe incluirse en ella.

6. Mejora no sólo la experiencia personal, sino también la coherencia de la colaboración en equipo.

Esta es una situación común que se subestima.

Cuando una persona escribe código, la diferencia entre finalización y async/await solo puede reflejarse en la fluidez. Pero en proyectos de colaboración entre varias personas, lo realmente importante de async/await es que hace que la expresión del equipo sea más unificada.

Ahora al menos es posible establecer de forma predeterminada:

  • Las capacidades asíncronas aparecerán en la función async
  • Error con throw
  • Utilice await como punto de pausa

Esta unificación es muy importante. Porque el mayor temor en proyectos complejos es que cada uno utilice su propio método de organización asincrónica. Tarde o temprano el proyecto será ejecutable, pero nadie podrá hacerse cargo de él rápidamente.

7. No soluciona nada

Llegados a este punto también debemos dejar claro que async/await no soluciona estos problemas:

  • No elimina automáticamente las condiciones de carrera.
  • No evita automáticamente que las solicitudes antiguas sobrescriban los nuevos resultados.
  • No decidirá por el equipo cómo se deben trazar los límites de estatus.
  • No garantiza automáticamente que las actualizaciones de la interfaz de usuario se produzcan en el contexto correcto

Entonces, si el diseño asincrónico original de un proyecto es muy desordenado, puede que sea “mejor lucir desordenado” después de reemplazarlo con async/await.

Esto hay que verlo claramente. async/await no es una panacea arquitectónica, simplemente ilumina muchos problemas que originalmente fueron encubiertos por la escritura.

8. Conclusión: Lo que resuelve es “¿Se puede mantener el código asincrónico durante mucho tiempo?”

Para decirlo en forma más breve, diría:

async/await La verdadera solución es que el código asincrónico se vuelve cada vez más difícil de organizar, razonar y mantener en proyectos complejos.

Entonces una afirmación más precisa no es:

“Hace que el código sea más corto”.

En lugar de eso:

“Devuelve el código asincrónico a la condición estructural para su mantenimiento a largo plazo”.