
Async/Await, otra forma de usar promesas en JavaScript
En un anterior artículo vimos como funcionaban las promesas en JavaScript. Sin embargo, existe otra forma de trabajar con promesas que nos permitirá escribir un código más simple y sencillo. Para ello hemos de utilizar las palabras clave async/await.
Hay que tener en cuenta que esta característica es nueva de EcmaScript 2017.
¿Como funciona async en JavaScript?
Si colocamos la palabra clave async delante de una función, estamos indicando que esta función devolverá una promesa. Y devolverá una promesa incluso en el caso de que no devolvamos una promesa de forma explícita. Veamos un sencillo ejemplo;
async function foo() { return 1; }
Esta sencilla función no va a devolver simplemente el número uno. Va a devolver una promesa ya resuelta que incluya dicho valor. De forma similar a los ejemplos que hemos visto hasta ahora donde al resolverse positivamente la promesa se devolvía un objeto de tipo Image, en este ejemplo simplemente se devuelve un objeto de tipo Number con el número uno.
var promesa = foo(); promesa.then(function(resultado){ document.write(resultado); document.close(); });
Tal y como puede observarse; la función foo() está devolviendo un objeto de tipo Promise a pesar de que no hemos creado ningún objeto explícitamente. Y la callback resolve recibe como parámetro un objeto con el valor devuelto por la función foo().
Por supuesto, nada nos impide devolver de forma explícita un objeto de tipo Promise.
async function foo() { return Promise.resolve(1); }
En este caso estamos usando el método estático resolve() de la clase Promise. Con este método podemos devolver un nuevo objeto de tipo Promise ya resuelto con el valor indicado por parámetro. También existe el método estático reject() que devuelve una promesa no resuelta satisfactoriamente.
Por lo tanto, async nos sirve para asegurarnos de que una función siempre devolverá una promesa, independientemente del valor que devuelva. Pero el verdadero motivo por el cual vamos a usar async es que solo dentro de las funciones que llevan la palabra clave async podemos usar la palabra clave await.
¿Como funciona await en JavaScript?
La palabra clave await, como su nombre indica, provoca que el navegador pause la ejecución del código JavaScript hasta que se resuelva la promesa implicada en dicho código. Esta pausa, no provoca ni un bloqueo en el navegador, ni el consumo significativo de ciclos de la CPU.
Es importante remarcar que await solo se puede usar dentro de una función async y que solo puede pausar la ejecución de JavaScript hasta que se cumpla una promesa (y no en cualquier otra situación).
Para los siguientes ejemplos vamos a usar una función del anterior artículo que nos sirve para cargar imágenes usando promesas.
function loadImg(src) { return new Promise(function(resolve, reject) { var img = new Image(); img.src = src; img.onload = () => { resolve(img); } img.onerror = () => { reject(new Error("Error al cargar la imagen: " + img.src)); } }); }
A modo de ejemplo, el código que habíamos re-escrito en el anterior artículo con funciones flecha lo podemos volver a escribir para usar async/await. De forma que este código;
var promesa = loadImg("sample1.jpg"); promesa.then( img => { document.write("Tamaño: " + img.width + "x" + img.height); } ).catch( error => { document.write(error.message); } ).finally( () => { document.close(); } );
Se podría re-escribir del siguiente modo con async/await:
async function ejecutar() { // función asíncrona! try { // en la siguiente línea, el código JavaScript se pausa hasta que la promesa se cumple var img = await loadImg("sample1.jpg"); // aqui ya tenemos la promesa cumplida y por lo tanto // podemos acceder al objeto Image sin problemas document.write("Tamaño: " + img.width + "x" + img.height); } catch(err) { // si ha habido algún error el código saltará aquí document.write(err.message); } document.close(); } ejecutar();
Algunos comentarios al respecto:
- Hemos tenido que colocar el código dentro de una función async para que el await funcione correctamente.
- La palabra clave await solo la podemos usar para pausar el código hasta que se cumpla la promesa.
- Una vez resuelta la promesa, la función loadImg() nos devuelve directamente el objeto Image en lugar del objeto Promise.
- El código después de loadImg() asume que la promesa se ha resuelto correctamente. En caso de error se produce una excepción que será capturada por el catch().
La principal ventaja de esta forma de trabajar con promesas, es que el código es más similar al código síncrono al que estamos acostumbrados. Tal vez en este ejemplo no parezca gran cosa, pero hagamos lo mismo con el ejemplo, del anterior artículo, en el que se cargaban dos imágenes de forma secuencial.
Este es el código original del anterior artículo:
// intentamos cargar la primera imagen var promesa = loadImg("sample1.jpg"); promesa.then( img => { // mostramos información de la primera imagen document.write("Tamaño: " + img.width + "x" + img.height + "<br>"); // intentamos cargar la segunda imagen return loadImg("sample2.jpg"); } ).then( img => { // mostramos información de la segunda imagen document.write("Tamaño: " + img.width + "x" + img.height); document.close(); } );
Es importante recordar que esto es solo un ejemplo y que cargar imágenes de forma secuencial no tiene mucho sentido. Sin embargo, en el caso de otro tipo de recursos, como por ejemplo scripts, puede resultar muy útil.
Veamos ahora la versión con async/await del código anterior, y además, esta vez con gestión de errores y la posibilidad de cargar fácilmente mas de dos imágenes:
(async () => { // función flecha anónima y asíncrona try { var pics = ["sample1.jpg", "sample2.jpg"]; for (var i=0;i<pics.length;i++) { var img = await loadImg(pics[i]); document.write("Tamaño: " + img.width + "x" + img.height + "<br>"); }; } catch(err) { document.write(err.message); } document.close(); })(); // los () al final hacen que se invoque inmediatamente
Tal y como se puede observar, el código dentro de la función asíncrona es más claro y sencillo, y no se parece en nada al típico código asíncrono. Para hacer el código más breve, en esta versión con async/await hemos usado además una función flecha anónima que invocamos inmediatamente tras su declaración.
También podemos cargar recursos de forma paralela con los métodos estáticos race() y all() de la clase Promise, ya que como vimos devuelven una promesa. Veamos cómo sería el ejemplo del método all() usando async/await:
(async () => { try { document.write("Cargando imágenes, por favor espera..."); var imagenes = ["sample1.jpg", "sample2.jpg", "sample3.jpg"]; var resultado = await Promise.all(imagenes.map(loadImg)); document.write("Todas las imágenes se han cargado con éxito!"); imagenes.forEach((src)=>{ var img = document.createElement('img'); img.src = src; document.body.appendChild(img); }); } catch (err) { document.write(err.message); } document.close(); })();
Y nada más. Espero que este artículo os haya resultado tan interesante como el anterior.