JavaScript Async/Await explicado en 10 minutos

Durante mucho tiempo, los desarrolladores de JavaScript tuvieron que depender de las devoluciones de llamada para trabajar con código asíncrono. Como resultado, muchos de nosotros hemos experimentado el infierno de la devolución de llamada y el horror que uno experimenta cuando se enfrenta a funciones que se ven así.

Afortunadamente, entonces (o deberíamos decir .then() ) vino Promesas. Ofrecieron una alternativa mucho más organizada a las devoluciones de llamada y la mayoría de la comunidad rápidamente pasó a usarlas.

Ahora, con la adición más reciente de Async/Await, ¡escribir código JavaScript está a punto de mejorar aún más!

¿Qué es Async/Await?

Async/Await es una característica de JavaScript esperada desde hace mucho tiempo que hace que trabajar con funciones asincrónicas sea mucho más agradable y fácil de entender. Está construido sobre Promises y es compatible con todas las API existentes basadas en Promises.

El nombre proviene de async y await - las dos palabras clave que nos ayudarán a limpiar nuestro código asíncrono:

Async - declara una función asíncrona (async function someName(){...} ).

  • Transforma automáticamente una función regular en una Promesa.
  • Cuando se las llama, las funciones asíncronas se resuelven con lo que se devuelva en su cuerpo.
  • Las funciones asíncronas permiten el uso de await .

Esperar:detiene la ejecución de funciones asíncronas. (var result = await someAsyncCall(); ).

  • Cuando se coloca frente a una llamada Promise, await obliga al resto del código a esperar hasta que la Promesa finalice y devuelva un resultado.
  • Await solo funciona con Promises, no funciona con callbacks.
  • Await solo se puede usar dentro de async funciones.

Aquí hay un ejemplo simple que, con suerte, aclarará las cosas:

Digamos que queremos obtener un archivo JSON de nuestro servidor. Escribiremos una función que use la biblioteca axios y envíe una solicitud HTTP GET a https://tutorialzine.com/misc/files/example.json. Tenemos que esperar a que el servidor responda, por lo que, naturalmente, esta solicitud HTTP será asíncrona.

A continuación podemos ver la misma función implementada dos veces. Primero con Promises, luego una segunda vez usando Async/Await.

// Promise approach

function getJSON(){

    // To make the function blocking we manually create a Promise.
    return new Promise( function(resolve) {
        axios.get('https://tutorialzine.com/misc/files/example.json')
            .then( function(json) {

                // The data from the request is available in a .then block
                // We return the result using resolve.
                resolve(json);
            });
    });

}

// Async/Await approach

// The async keyword will automatically create a new Promise and return it.
async function getJSONAsync(){

    // The await keyword saves us from having to write a .then() block.
    let json = await axios.get('https://tutorialzine.com/misc/files/example.json');

    // The result of the GET request is available in the json variable.
    // We return it just like in a regular synchronous function.
    return json;
}

Está bastante claro que la versión Async/Await del código es mucho más corta y fácil de leer. Aparte de la sintaxis utilizada, ambas funciones son completamente idénticas:ambas devuelven Promesas y se resuelven con la respuesta JSON de axios. Podemos llamar a nuestra función asíncrona así:

getJSONAsync().then( function(result) {
    // Do something with result.
});

Entonces, ¿Async/Await vuelve obsoletas las promesas?

No, en absoluto. Cuando trabajamos con Async/Await, todavía usamos Promises bajo el capó. Una buena comprensión de Promises realmente lo ayudará a largo plazo y es muy recomendable.

Incluso hay casos de uso en los que Async/Await no funciona y tenemos que volver a Promises para obtener ayuda. Uno de esos escenarios es cuando necesitamos hacer múltiples llamadas asincrónicas independientes y esperar a que terminen todas.

Si intentamos hacer esto con async y await, ocurrirá lo siguiente:

async function getABC() {
  let A = await getValueA(); // getValueA takes 2 second to finish
  let B = await getValueB(); // getValueB takes 4 second to finish
  let C = await getValueC(); // getValueC takes 3 second to finish

  return A*B*C;
}

Cada llamada en espera esperará a que la anterior devuelva un resultado. Como estamos haciendo una llamada a la vez, la función completa tardará 9 segundos de principio a fin (2+4+3).

Esta no es una solución óptima, ya que las tres variables A , B y C no son dependientes unos de otros. En otras palabras, no necesitamos saber el valor de A antes de obtener B . Podemos obtenerlos al mismo tiempo y ahorrar unos segundos de espera.

Para enviar todas las solicitudes al mismo tiempo un Promise.all() es requerido. Esto asegurará que todavía tengamos todos los resultados antes de continuar, pero las llamadas asincrónicas se dispararán en paralelo, no una tras otra.

async function getABC() {
  // Promise.all() allows us to send all requests at the same time. 
  let results = await Promise.all([ getValueA, getValueB, getValueC ]); 

  return results.reduce((total,value) => total * value);
}

De esta manera la función llevará mucho menos tiempo. El getValueA y getValueC las llamadas ya habrán terminado a la hora getValueB termina En lugar de una suma de tiempos, reduciremos efectivamente la ejecución al tiempo de la solicitud más lenta (getValueB - 4 segundos).

Manejo de errores en Async/Await

Otra gran ventaja de Async/Await es que nos permite detectar cualquier error inesperado en un buen bloque try/catch antiguo. Solo necesitamos envolver nuestro await llamadas como esta:

async function doSomethingAsync(){
    try {
        // This async call may fail.
        let result = await someAsyncCall();
    }
    catch(error) {
        // If it does we will catch the error here.
    }  
}

La cláusula catch manejará los errores provocados por las llamadas asincrónicas esperadas o cualquier otro código fallido que hayamos escrito dentro del bloque try.

Si la situación lo requiere, también podemos detectar errores al ejecutar la función asíncrona. Dado que todas las funciones asíncronas devuelven Promesas, simplemente podemos incluir un .catch() controlador de eventos al llamarlos.

// Async function without a try/catch block.
async function doSomethingAsync(){
    // This async call may fail.
    let result = await someAsyncCall();
    return result;  
}

// We catch the error upon calling the function.
doSomethingAsync().
    .then(successHandler)
    .catch(errorHandler);

Es importante elegir qué método de manejo de errores prefiere y apegarse a él. El uso de try/catch y .catch() al mismo tiempo probablemente generará problemas.

Soporte del navegador

Async/Await ya está disponible en la mayoría de los principales navegadores. Esto excluye solo IE11:todos los demás proveedores reconocerán su código asíncrono/en espera sin necesidad de bibliotecas externas.

Los desarrolladores de nodos también pueden disfrutar del flujo asincrónico mejorado siempre que estén en el nodo 8 o posterior. Debería convertirse en LTS a finales de este año.

Si esta compatibilidad no le satisface, también hay varios transpiladores JS como Babel y TypeScript, y la biblioteca Node.js asyncawait que ofrece sus propias versiones multiplataforma de la función.

Conclusión

Con la adición de Async/Await, el lenguaje JavaScript da un gran paso adelante en términos de legibilidad del código y facilidad de uso. Tanto los principiantes en JavaScript como los programadores veteranos apreciarán la capacidad de escribir código asíncrono que se asemeje a las funciones síncronas normales.

  • Asíncrono en MDN
  • Esperar en MDN
  • Async/Await:el héroe JavaScript merecido
  • ¿De dónde vino Async/Await y por qué usarlo?