En principio, es posible que hayas leído que en Javascript sólo se puede utilizar un await
en el interior de funciones declaradas como async
. Por lo tanto, si tuvieramos que ejecutar una función asíncrona con await
en nuestro fichero Javascript principal (contexto global), tendríamos que envolver el await
en una función declarada con async
.
Crear una función asíncrona sólo para utilizar un
await
se siente bastante artificial y complicado, y aunque la teoría es cierta, no siempre tiene que ser así.
¿Qué es top-level await
?
En await
aunque no nos encontremos en funciones declaradas como async
.
Para ello, debes cumplir dos condiciones:
- Estar en el nivel superior, o sea, en el contexto global.
- Debe tratarse de Javascript en un módulo ESM. Si estás cargando un
.js
en el navegador sin<script type="module">
o estás usando NodeJS con CommonJS no podrás usar esta característica.
Si no cumplimos las condiciones anteriores, es posible que nos aparezca un error similar a este en nuestro navegador:
Uncaught SyntaxError:
await
is only valid inasync
functions and the top level bodies of modules
Esto ocurre porque, como bien dice el mensaje de error, estamos ejecutando await
en un lugar que no es asíncrono, y debemos ejecutarlo en un contexto asíncrono.
Ejemplo sin top-level await
Veamos un ejemplo donde solicitamos el archivo /robots.txt
, obtenemos su contenido y lo mostramos por consola:
<script>
async function init() {
const response = await fetch("/robots.txt");
const data = await response.text();
return data;
}
const data = await init();
console.log(data);
</script>
Si pulsamos en DEMO, aunque veremos una pantalla blanca porque no renderiza nada, si vamos a la consola, veremos el error await is only valid in async functions and the top level bodies of modules
.
Ejemplo con top-level await
Sin embargo, veamos ahora que ocurre si definimos el mismo código anterior en un módulo ESM, utilizando el <script type="module">
. Si examinamos la consola del navegador, comprobaremos que si se muestra el contenido correctamente:
<script type="module">
async function init() {
const response = await fetch("/robots.txt");
const data = await response.text();
return data;
}
const data = await init();
console.log(data);
</script>
Como hemos mencionado, el await
si que se puede ejecutar en el contexto global sin necesidad de envolverlo en una función async
si estamos en un módulo ESM.
Solución en otros entornos
Si estamos utilizando sistemas antiguos CommonJS, síncronos, o que simplemente no soportan el top-level await, se puede solucionar este problema envolviendo el código en una función asíncrona y auto-ejecutarla, como se muestra en el siguiente fragmento de código:
<script>
async function init() {
const response = await fetch("/robots.txt");
const data = await response.text();
return data;
}
(async function() {
const data = await init();
console.log(data);
})();
</script>
Sin embargo, el código de la solución dista de ser una solución elegante. La mejor opción sería migrar a un sistema donde podamos utilizar top-level await:
- Cargando Javascript con un
<script type="module">
en lugar de un<script>
(Navegador). - Añadiendo un
"type": "module"
en elpackage.json
de nuestro proyecto (NodeJS).
Sin embargo, son cambios que requieren conocer de arquitectura o realizar profundos cambios en nuestro proyecto, por lo que sólo se podrían aplicar inmediatamente si nos encontramos empezando un nuevo proyecto.