⏱️ Lectura: 12 min
ClojureScript dio un paso importante en su evolución como lenguaje compilable a JavaScript: la versión 1.12.145, publicada el 7 de mayo de 2026, incorpora soporte nativo para funciones async/await mediante un simple hint de metadata. Para los desarrolladores que escriben clojurescript async y necesitan interactuar con APIs modernas del navegador o librerías basadas en promesas, este cambio elimina la necesidad de envolver código en macros o sumar dependencias externas.
📑 En este artículo
- Qué pasó: ClojureScript 1.12.145 estrena el hint ^:async
- Contexto e historia: ClojureScript y la deuda con JavaScript moderno
- Datos y cifras: el hint ^:async en la práctica
- Impacto y análisis: por qué importa para LATAM
- Cómo migrar tu código existente
- Diagrama: cómo se compila una función async
- Qué sigue
- Preguntas frecuentes
- Referencias
El anuncio, hecho por el equipo oficial de ClojureScript en su sitio, marca el primer ajuste relevante después de la migración del compilador a ECMAScript 2016 como target. Lo curioso del cambio no es la complejidad técnica, sino lo silencioso de su llegada: una característica que dominó la lista de mejoras más pedidas en la última Clojure Survey termina implementándose con apenas un caracter de notación.
Qué pasó: ClojureScript 1.12.145 estrena el hint ^:async
El equipo de ClojureScript, mantenido por la comunidad alrededor de Cognitect y respaldado por Rich Hickey, liberó la versión 1.12.145 con un cambio puntual pero de alto impacto: la posibilidad de marcar funciones con metadata ^:async para que el compilador emita una función asíncrona nativa de JavaScript en lugar de la implementación tradicional basada en core.async o macros como las que ofrece la librería promesa.
El uso es directo. Cuando una función se anota con ^:async, el compilador detecta llamadas a (await ...) dentro del cuerpo y las traduce a la sintaxis nativa await de JavaScript. La función resultante es indistinguible de una async function escrita a mano en JS, lo que significa que se puede pasar a librerías que esperan promesas, encadenar con .then() o invocar desde código JavaScript externo sin envolturas.
(refer-global :only '[Promise])
(defn ^:async foo [n]
(let [x (await (Promise/resolve 10))
y (let [y (await (Promise/resolve 20))]
(inc y))
f (fn [] 20)]
(+ n x y (f))))
El soporte se extiende a los tests con clojure.test: marcando un deftest con ^:async, el harness de pruebas espera correctamente la resolución de promesas antes de evaluar las aserciones. Esto resuelve uno de los puntos más frustrantes del testing en ClojureScript, donde los tests asincrónicos requerían setup adicional o macros como async de cljs.test.
Contexto e historia: ClojureScript y la deuda con JavaScript moderno
ClojureScript nació en 2011 como una variante del lenguaje Clojure que compila a JavaScript. Durante más de una década, el equipo se mantuvo conservador con el target del compilador, generando código compatible con navegadores antiguos para garantizar la portabilidad. La consecuencia: features modernas de JavaScript como async/await, Symbol, generadores nativos o BigInt no estaban disponibles directamente desde el lenguaje.
Quienes querían usar promesas en ClojureScript tenían tres caminos. El primero, core.async, ofrecía channels y go blocks pero introducía un modelo mental distinto al de la comunidad JavaScript. El segundo, librerías como promesa de Funcool, daban macros como p/let que simulaban await pero requerían adoptar una dependencia externa. El tercero, el más sucio, era escribir .then manual con callbacks anidados.
El salto a ECMAScript 2016 como target del compilador, que ocurrió en una versión anterior, abrió la puerta a aprovechar features modernas. El equipo dejó claro que la elección sería selectiva: no todo lo nuevo de JavaScript se incorporaría por defecto, solo aquello que aportara valor real a la interop. Las funciones async cumplen ese criterio porque son hoy el estándar de facto para trabajar con APIs del navegador, fetch, IndexedDB, Web Workers y la mayoría de librerías npm.
Datos y cifras: el hint ^:async en la práctica
La implementación es minimalista. El hint ^:async es metadata estándar de Clojure que se aplica a la función. Cuando el compilador procesa la definición, verifica esta metadata y genera código JavaScript con la palabra reservada async en la declaración. Las llamadas a (await expr) dentro del cuerpo se traducen a la expresión await expr de JavaScript, respetando el ámbito léxico.
Tres detalles técnicos que vale destacar:
- Closures internas no heredan — Una función anónima creada dentro del cuerpo de una función async no es automáticamente async. El ejemplo del anuncio lo deja claro:
(fn [] 20)dentro defoosigue siendo síncrona y no admiteawait. - Compatible con apply — El test del anuncio muestra que se puede invocar la función async con
applyy la promesa resultante funciona igual:(await (apply foo [10]))devuelve el mismo valor que la llamada directa. - Manejo de errores con try/catch — Los rechazos de promesas se propagan como excepciones dentro del bloque async, capturables con
(catch :default _ ...)de manera idéntica a un try/catch sincrónico, lo que mantiene consistencia con el resto del lenguaje.
💡 Tip: Si vienes de core.async, no hace falta migrar todo. Las funciones async y los go blocks pueden convivir en el mismo proyecto. Usá ^:async para interop con APIs basadas en promesas y core.async para flujos de datos internos con backpressure.
Sobre el contribuidor: el cambio fue aportado por Michiel Borkent, una figura conocida en la comunidad Clojure. Borkent es el creador de Babashka, nbb y squint, herramientas que ya ofrecían interop con async/await fuera del compilador oficial. Que la implementación canónica venga de su mano da continuidad técnica entre los proyectos del ecosistema y reduce la fragmentación.
Impacto y análisis: por qué importa para LATAM
Para los equipos que usan ClojureScript en producción —en LATAM se concentran en startups financieras de Argentina y Brasil, agencias de Chile y Colombia, y proyectos de gobierno digital en México y Uruguay— el impacto es práctico y medible:
- Menos código boilerplate — Reemplazar
p/letde promesa o canales de core.async por^:asyncyawaitreduce líneas y simplifica el flujo de control. - Menor superficie de dependencias — Cada librería externa es deuda técnica futura. Tener async/await nativo significa una dependencia menos en el bundle final, lo que también reduce tamaño descargado para usuarios con conexiones lentas, un factor relevante en mercados con redes móviles dominantes.
- Mejor onboarding — Los desarrolladores nuevos que llegan desde JavaScript o TypeScript reconocen
async/awaital instante. La curva de aprendizaje del paradigma asíncrono se aplana y facilita justificar la adopción del lenguaje en equipos mixtos. - Tests más simples — La integración con
deftestevita el lío de envolver aserciones en callbacks. Un test async se lee como un test síncrono, lo que mejora la mantenibilidad a largo plazo.
Vale poner el cambio en perspectiva. ClojureScript no es un lenguaje masivo. Las estadísticas de descargas en Clojars y Maven Central sugieren que el ecosistema entero suma cerca de 50.000 desarrolladores activos en el mundo, una fracción del millonario universo de JavaScript. Pero los proyectos que lo usan tienden a ser de alta calidad técnica: Nubank construyó su core bancario en Clojure y usa ClojureScript en su frontend, Cognitect (hoy parte de Nubank) lo creó, y CircleCI tuvo gran parte de su pipeline en él durante años.
El impacto, entonces, es de palanca: una mejora pequeña en una herramienta usada por equipos con alto leverage técnico se traduce en miles de horas ahorradas globalmente. Y el patrón de adopción del feature seguirá la misma curva que vimos con TypeScript estricto o ESM en Node: lento al principio, dominante en cuestión de meses una vez que las herramientas tooling se actualizan.
Cómo migrar tu código existente
Si ya tenés un proyecto ClojureScript en producción, los pasos para empezar a usar el feature son directos.
Instalación: actualizar la dependencia
En tu archivo deps.edn:
{:deps {org.clojure/clojurescript {:mvn/version "1.12.145"}}}
Para Leiningen en project.clj:
(defproject mi-proyecto "0.1.0"
:dependencies [[org.clojure/clojurescript "1.12.145"]])
Para shadow-cljs en shadow-cljs.edn:
{:dependencies [[thheller/shadow-cljs "2.27.0"]
[org.clojure/clojurescript "1.12.145"]]}
Primer ejemplo: fetch a una API REST
(refer-global :only '[fetch])
(defn ^:async cargar-usuario [id]
(let [resp (await (fetch (str "/api/users/" id)))
data (await (.json resp))]
{:id (.-id data)
:nombre (.-nombre data)
:email (.-email data)}))
El equivalente con la librería promesa requería más código y una dependencia adicional:
(require '[promesa.core :as p])
(defn cargar-usuario [id]
(p/let [resp (fetch (str "/api/users/" id))
data (.json resp)]
{:id (.-id data)
:nombre (.-nombre data)
:email (.-email data)}))
⚠️ Ojo: Toda función que use ^:async retorna una Promise, incluso cuando el cuerpo no espera nada. Quien la consume debe estar preparado para manejarla como promesa. Esto puede romper código existente que asumía retorno sincrónico — revisá los call sites antes de migrar masivamente.
Diagrama: cómo se compila una función async
flowchart LR
A["Codigo CLJS con metadata async"] --> B["Compilador ClojureScript 1.12.145"]
B --> C["Detecta hint async"]
C --> D["Detecta llamadas await"]
D --> E["Emite async function de JS"]
E --> F["JavaScript ejecutable en navegador"]
Qué sigue
El equipo de ClojureScript no ha publicado un roadmap detallado, pero el patrón establecido sugiere que vendrán más enhancements de interop con JavaScript moderno. Los candidatos naturales son:
- Generadores nativos — Soporte para
function*yyieldde JavaScript, útil para iteradores lazy en interop con librerías que los usan. - Top-level await — Permitir
(await ...)en el toplevel de un namespace para flujos de inicialización con módulos ESM. - Symbol y WeakRef — Mayor cobertura de las primitivas modernas de JavaScript que hoy requieren acceso vía interop directa.
- Mejor source maps para async — Las funciones async tradicionalmente complican el debugging; mejorar los mapeos sería un siguiente paso natural para mantener la productividad.
📌 Nota: La Clojure Survey 2026 cierra a fines de año. Si querés influir en las prioridades del próximo ciclo, participá. Las features más pedidas se priorizan en el roadmap del año siguiente, como pasó con async/await este año.
Para la comunidad hispana, este cambio reduce la fricción para evaluar ClojureScript en proyectos nuevos. Antes, justificar la curva de aprendizaje era difícil cuando había que sumar core.async o promesa al estack mental. Ahora, un desarrollador con experiencia en JavaScript puede leer código ClojureScript async y reconocer la semántica al instante, lo que hace al lenguaje más accesible para equipos sin background previo en Lisp.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Qué versión de Node.js o navegador necesito?
El target ECMAScript 2016 cubre todos los navegadores modernos (Chrome 55+, Firefox 52+, Safari 10.1+, Edge 15+) y Node.js 7.6 o superior. Si tu base de usuarios incluye Internet Explorer 11, necesitarás transpilar el output con Babel o cambiar el target del compilador a una versión anterior de ES.
¿Puedo seguir usando core.async junto con ^:async?
Sí. Ambos modelos coexisten sin conflicto. core.async sigue siendo la mejor opción para channels con backpressure, sistemas de colas internas y coordinación de procesos largos. Las funciones async/await son ideales para interop con APIs basadas en promesas y operaciones puntuales como fetch o lectura de archivos.
¿Funciona con shadow-cljs y Figwheel?
Sí, el feature está implementado a nivel del compilador de ClojureScript, así que cualquier herramienta de build que use el compilador oficial lo soporta automáticamente. shadow-cljs 2.27 y Figwheel Main son compatibles. Verificá que la versión transitiva de ClojureScript en tu lockfile sea 1.12.145 o superior antes de probar.
¿El hint ^:async funciona con multimethods o protocolos?
Solo aplica a funciones declaradas con defn o fn. Para multimethods, debés marcar cada implementación específica como async. Para protocolos, el método debe ser declarado async en cada extensión, ya que el compilador genera funciones individuales por implementación de protocolo.
¿Cómo manejo errores en una función ^:async?
Usá try/catch dentro del cuerpo de la función. Los rechazos de promesas se transforman en excepciones dentro del scope async. La forma (catch :default e ...) captura cualquier error, mientras que tipos específicos como (catch js/Error e ...) filtran por instancia y permiten manejo selectivo.
¿Hay impacto en el tamaño del bundle?
Marginal. Una función async genera código JavaScript ligeramente más largo que la versión equivalente con .then, pero el ahorro de eliminar dependencias como promesa (que pesa unos 8KB minificado y gzipped) compensa con creces el incremento. En proyectos medianos, esperá una reducción neta de 3 a 7KB en el bundle final.
Referencias
- ClojureScript 1.12.145 Release Notes — Anuncio oficial del equipo de ClojureScript con el detalle del hint ^:async.
- Repositorio oficial de ClojureScript en GitHub — Código fuente del compilador, changelog completo y issue tracker público.
- Guía Quick Start de ClojureScript — Documentación oficial para empezar a usar el lenguaje desde cero.
- MDN: async function en JavaScript — Referencia técnica de la palabra reservada async en ECMAScript.
- Sobre ClojureScript en clojure.org — Filosofía de diseño y relación con el lenguaje Clojure.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
0 Comentarios