Async JS: Guía completa sobre código asíncrono – Parte #1

¿Qué es el código asíncrono en JavaScript?

La capacidad de JavaScript (JS) de ser asíncrono (async) es una de las cualidades más poderosas de JS. Utilizamos async JS cuando queremos llevar a cabo tareas que tardan cierto tiempo en ejecutarse, como hacer una llamada a una base de datos y esperar a recibir dichos datos. ⏳

Un bloque de código asíncrono es aquel que puede empezar a ejecutarse en un momento determinado, detenerse cuando se lo pidamos y seguir ejecutándose más tarde. 

¿Entonces, qué es el código síncrono?

JS es por definición un lenguaje de código síncrono. Es decir, por defecto, lee una línea de código detrás de otra, de arriba a abajo. Lo que significa que no leerá la segunda línea de código hasta que haya terminado de leer la primera, etc.

ORDEN DE EJECUCIÓN:

console.log('hola');
console.log('qué');
console.log('tal');
<-- 1ª línea en ejecutarse
<-- 2ª línea en ejecutarse
<-- 3ª línea en ejecutarse

El problema con el código síncrono, como mencionamos arriba, es que hay ocasiones en las que necesitamos ejecutar una función que requiere cierto tiempo, y si tuviésemos que esperar a que JS resolviese ese bloque de código para poder pasar al siguiente, el resultado no sería nada eficiente. ?

Es decir, si tenemos una serie de funciones:

Código síncrono

donde la función 2 es un código que llama a una base de datos para pedirle cierta información, la función 3 debería esperar a que la función 2 recibiese esa información de la base de datos para poder ejecutarse. ?

Eso es lo que ocurriría en un código síncrono. Así, la función 2 bloquearía la ejecución del resto del código, convirtiéndose así en un blocking code. ✋?

Si imaginamos esto a una escala mayor, podemos entender que usar código síncrono ralentizaría tanto la ejecución de nuestro código que el resultado sería desastroso. Por esta razón necesitamos utilizar código asíncrono.

esquema async

Aquí la función 2 es async, lo que hace que se empiece a ejecutar, y, cuando llega a una parte que requiere tiempo para ejecutarse, el resto de funciones siguen su secuencia de ejecución en paralelo.

Es decir, JS crea varias "secuencias de ejecución", dos en este caso. Y destina dos partes distintas del navegador para ejecutarlas. Como si fueran dos corrientes de agua. ? ?

Cuando finalmente la primera parte de la función 2 se haya ejecutado (ha llegado la información de la base de datos), la función 2 sigue su ejecución con la ayuda de una callback function que le debemos pasar por parámetro. Hablaremos de esa callback function en la segunda parte de esta serie, no sufras. ? ​​

Y con este sistema se solucionan los problemas de códigos síncronos que bloquean la secuencia de ejecución de JS. ?

Código asíncrono en acción

JS viene con funciones async por defecto, como es la función setTimeout(). Vamos a demostrarlo. Trabajaremos con un archivo HTML y un archivo JS. Los llamaremos index.html sandbox.js respectivamente. 

No olvides vincular el sandbox.js al index.html. 

1. Creamos un boilerplate para nuestro index.html, y así nos queda:​​​​​​

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Async JS</title>
</head>
<body>
    <script src="sandbox.js"></script>
</body>
</html>

2. En el sandbox.js, creamos unos cuantos console.log y nos fijamos en el orden en el que se imprimen en la consola. 

console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);

Nada inesperado, se imprimen en el orden que suponíamos, del 1 al 5.

3. Si ahora creamos una función setTimeout por en medio, y le decimos que se ejecute al cabo de 2 segundos, ¿bloqueará la secuencia de ejecución? ?

console.log(1);
console.log(2);
console.log(3);

setTimeout(() => {
    console.log('I am the callback function that just fired!');
}, 2000);

console.log(4);
console.log(5);

// 1
// 2
// 3
// 4
// 5
// I am the callback function that just fired!

¡No! Porque setTimeout es una async function, así que lo que ocurre es que ​​JS empieza a leer la secuencia de código de arriba a abajo, y cuando llega al setTimeout, lo coloca en otra línea secuencial y sigue leyendo el código. Es lo que menciona arriba sobre las dos corrientes de agua.

Cuando el segundo parámetro del setTimeout se cumple (cuando pasan 2 segundos), JS lo ejecuta, imprimiendo por tanto el console.log al final.​​​​​​

El setTimeout es sólo una forma de simular que estamos solicitando información de algún servidor o una base de datos. Así que vamos a pasar a la realidad y a interactuar verdaderamente con bases de datos y servidores, sabiendo ya como sabemos los principios básicos del código async. ?

Nuestras primeras HTTP Requests

Una HTTP request es una de las acciones de las que hablábamos antes que pueden tardar cierto tiempo en realizarse. Por eso, este tipo de acciones deben llevarse acabo usando async code. ?

Usamos HTTP requests cuando queremos obtener cierta información almacenada en una base de datos o en un servidor para posteriormente utilizarla según nuestras necesidades.

? Por cierto, a las HTTP requests también se las llama network requests o simplemente requests.

Para captar esa información debemos apuntar a los llamados API endpointsque son sencillamente URLs que un servidor o una API nos facilita.

APIs hay de muchos tipos, por ejemplo de música, como Spotify, o streaming, como Netflix, y millones más. Si por ejemplo quisiéramos hacer una request a una API como Netflix, la visión global sería algo así:

Netflix API endpoint: http://www.netflix.com/pixar​​​​​​​​

http request y API esquema

Y con esto obtendríamos una lista con las películas de Pixar.

Lo habitual al obtener una respuesta (responsedesde el servidor / API es obtenerla en formato JSON, un formato muy parecido a los objetos de JS. 

Para practicar todos estos conceptos vamos a usar una API gratuita llamada JSONPlaceholder. Verás que en su página principal tienen varios ejemplos sobre cómo usarla. Si vas a Examples y le das a Try it, te devolverá información en formato JSON:

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Así será el tipo de información que recibamos de un servidor, así que cuanto antes nos acostumbremos, mejor. ? Al darle a Try it hemos hecho una http request al API endpoint https://jsonplaceholder.typicode.com/todos/1Verás que, si copias esa dirección directamente en el navegador, nos devuelve el mismo JSON que antes.

? Y aquí viene lo interesante. Si vas a tu navegador y abres la dev tools, comprobarás que hay cierta información en la pestaña network (recarga la página si no la ves).

network GET request

El nombre de nuestra request es 1, y a la derecha podemos ver cosas como el tipo de request que hemos hecho (cuando queremos obtener información, se dice que hacemos una GET request), el estado (status) de nuestra request, y mucho más. 

HTTP Requests con XHR

Desde la web de JSONPlaceholder hemos hecho una pequeña simulación sobre cómo hacer una http request. Ahora vamos a hacer una de verdad, usando nuestro código JS. Vamos a trabajar en nuestro archivo sandbox.js. Borra su contenido o coméntalo para que no nos moleste.

1. Lo primero que debemos hacer es crear un request object.

const request = new XMLHttpRequest();

​La parte XML representa a un formato antiguo para trabajar con datos, que se utilizaba antes de que el formato JSON apareciera en escena. Pero ahora, XML + HttpRequest forman un objeto que puede trabajar con todo tipo de formatos de datos, tanto antiguos como modernos. ?

Con el request object creado, ya podemos usarlo para enviar una petición (request) para obtener cierta información (data). Para hacer una ​​data request, vaya​​. ?

2. ​​Nuestro request object viene repleto de propiedades y métodos específicos para hacer data requests, como el método open()Este método acepta dos argumentos: el tipo de request que queremos hacer y desde dónde queremos hacer la request.

request.open('tipo de request', 'API endpoint');​​​​​​ 

Cuando queremos obtener (get) cierta información, el tipo de request sería GET.

Nuestro API endpoint lo sacamos del ejemplo de JSONPlaceholder, pero sin el final, porque eso hace referencia a una tarea (todo) concreta, y nosotros queremos obtener una lista de tareas (todos). 

request.open('GET', 'https://jsonplaceholder.typicode.com/todos');

TIPOS DE DATA REQUESTS MÁS COMUNES

  • GET
  • POST
  • PUT
  • DELETE

3. Pero lo cierto es que lo único que hace el método open() es preparar la request. Para enviarla, debemos usar el método send().

request.send();

Si ahora guardas y vas a la pestaña network de tus dev tools, verás que nuestra request está hecha y hemos obtenido una response.​​​​​​

request hecha

4. Pero sólo haciendo esto no tenemos manera de saber cuándo se ha completado nuestra request ni cómo acceder a esa información obtenida como response. Pero esto tiene solución, porque podemos verificar el estado de nuestra request añadiéndole un event listener vinculado a un event llamado readystatechange.

Por lo que se intuye de su nombre, este event se dispara cada vez que hay un cambio en el estado (state) de nuestra request. Existen 4 estados en los que puede encontrarse un request (bueno, 5, pero el 0 es sencillamente que no hay ninguna request hecha).​​​​​​​​​​​​​​​​​​​​

FASES (STATES) DE UNA REQUEST

  • 0 - UNSENT
  • 1 - OPENED
  • 2 - HEADERS_RECEIVED
  • 3 - LOADING
  • 4 - DONE

? Para ver cada uno de estos states, vamos a imprimirlos haciendo dos console.log: uno mostrando toda la info de la request y otro mostrando el número de la fase en la que se encuentra nuestra request, usando una propiedad llamada readyState.

const request = new XMLHttpRequest();

request.addEventListener('readystatechange', () => {
    console.log(request);
    console.log(request.readyState);
});

request.open('GET', 'https://jsonplaceholder.typicode.com/todos');
request.send();

¡Y voilà! En tu consola deberías ver que se han impreso todas las fases de una request. ?

esquema fases request

MDN tiene una documentación muy completa sobre los states de una request. En el momento de escribir este post, la documentación en español difiere de la documentación en inglés, así que lo prudente en estos casos y recurrir al inglés, ya que es el idioma en el que (seguramente) se escribió primero la documentación.

La fase 4 es la más importante, ya que es la fase que nos permite recibir la información y hacer algo con ella. Verás que esa información está en el request object (puedes verlo en tu consola si despliegas uno de tus console.log), concretamente en la propiedad responseText:

  • XMLHttpRequest {onreadystatechange: null, readyState: 1, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
    1. onabort: null
    2. onerror: null
    3. onload: null
    4. onloadend: null
    5. onloadstart: null
    6. onprogress: null
    7. onreadystatechange: null
    8. ontimeout: null
    9. readyState: 4
    10. response: "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    11. responseText: "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    12. responseType: ""
    13. responseURL: "https://jsonplaceholder.typicode.com/todos"
    14. responseXML: null
    15. ...

    5. Así que lo que podemos hacer dentro de nuestro event listener es comprobar si nuestra request está en state 4, y de ser así, ya podemos coger la response y hacer algo con ella. Vamos a hacer simplemente un console.log imprimiendo la propiedad responseText. 

    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4) {
            console.log(request.responseText);
        }
    });
    

    Y con esto comprobarás en tu consola que nos ha llegado una respuesta con un montón de código en formato JSON, que parece más que nada un gigantesco ​string lleno ​​de objetos​​. ?‍♂️

    Cómo gestionar los diferentes estados de una response

    Comprobar que nuestra request ha sido enviada no es suficiente, ya que también debemos comprobar si ha habido algún error en el envío. Por ejemplo, puede suceder que el API endpoint que especifiquemos no sea correcto:

    request.open('GET', 'https://jsonplaceholder.typicode.com/todosXXX');

    Esta errata no impediría a la request superar todas las fases y alcanzar la fase 4, con la diferencia de que no obtendríamos ninguna información de JSONPlaceholder, porque no estamos escribiendo la dirección correcta.

    1. Además, también recibiremos un código de error al respecto. Probemos a hacer un console.log de la request para ver qué nos devuelve.

    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4) {
            console.log(request, request.responseText);
        }
    });
    
    request.open('GET', 'https://jsonplaceholder.typicode.com/todosXXX');
    request.send();
    

    Y ahí lo tenemos, un bonito error 404. ?

    Failed to load resource: the server responded with a status of 404 ()

    ? Fíjate que el error 404 viene dentro de una propiedad de la request llamada statusOjo con no confundirla con la propiedad readyState. Mientras readyState nos indica la fase de la request en la que nos encontramos, status nos indica el estado de la request. No es lo mismo. ?

    Además, nuestro console.log del request nos devuelve un responseText con un string vacío:

    responseText: "{}"​​​​​​

    Por tanto, cuando hacemos una petición (request)debemos comprobar tanto la fase de nuestra request como el estado de la respuesta (response) que recibimos. Si corregimos nuestro API endpoint a su estado anterior y enviamos la request, verás que ahora el status es 200, que significa que todo ha salido bien. 

    Existen muchos tipos de response status codes. Aquí tienes una guía completa de MDN sobre ellos.

    2. Así que debemos hacer otra comprobación para asegurarnos de que el status sea 200. De esta manera, nuestra request sólo se enviará si se cumplen esas dos condiciones:

    • readyState: 4
    • status: 200
    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4 && request.status === 200) {
            console.log(request, request.responseText);
        }
    });
    
    request.open('GET', 'https://jsonplaceholder.typicode.com/todosXXX');
    request.send();
    

    Y ahora, aunque en la consola seguimos recibiendo un error 404, verás que nuestro console.log no se ha disparado. ?

    3. Lo que podemos hacer es añadir una comprobación más (un if else statement) que compruebe únicamente si el readyState es 4. Así, si se dispara el bloque del if else, sabremos que hemos hecho algo mal en nuestra request. ?‍♀️ Le añadimos un console.log dentro de ese bloque, indicando que no hemos podido obtener la información.

    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4 && request.status === 200) {
            console.log(request, request.responseText);
        } else if(request.readyState === 4) {
            console.log('ooops...we could not fetch the data');
        }
    });
    

    ​​​​​​​​​​​Y ahora deberíamos ver ese mensaje en la consola si escribimos mal el nombre de nuestro API endpoint.

    ? Nuestro siguiente paso en esta aventura para aprender a hacer http requests es aprender a escribir callback functions que gestionen lo que sucederá cuando recibamos los datos que hemos solicitado (el rectangulito amarillo que corresponde a la 2ª fase de nuestro esquema de arriba). 

    ¡Pero eso lo veremos en la segunda parte de esta serie! ?

    THE END!

    ¡Y con esto terminamos la primera parte de esta Async Saga! Espero que hayas aprendido algo nuevo ?.  Si te queda alguna duda, ¡nos vemos en los comentarios!

    Si quieres seguir aprendiendo, aquí tienes la parte #2.

    Sobre la autora de este post

    Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí. 

    Otros artículos que pueden interesarte

    Cómo aprendí a programar cuando estaba «programada» para ser de letras
    [tcb-script src="https://player.vimeo.com/api/player.js"][/tcb-script]A nadie le gusta su trabajo. Eso es lo que me decía a mí misma cuando conseguí mi primer[...]
    Días del 160 al 203 – ¡Primer objetivo conseguido!
    “A veces podemos pasarnos años sin vivir en absoluto, y de pronto toda nuestra vida se concentra en un solo[...]
    Claves para entender Angular. Qué es y cómo se utiliza
    Angular es un framework creado por Google que nos permite construir Single Page Applications (SPA, por sus siglas en inglés).Frameworks¿Pero qué es[...]
    Si crees que este post puede serle útil a alguien, por favor, ¡compártelo!:

    2 comentarios en «Async JS: Guía completa sobre código asíncrono – Parte #1»

    1. Excelente curso y contenido en general, me llamó mucha la atención y créeme tienes un seguidor nuevo, ando aprendiendo tecnologías nuevas y tu blog me cae como anillo al dedo. Felicidades y saludos desde México

    Deja un comentario

    Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

    Esta web utiliza cookies para asegurar que se da la mejor experiencia al usuario. Si continúas utilizando este sitio se asume que estás de acuerdo. más información

    Los ajustes de cookies en esta web están configurados para «permitir las cookies» y ofrecerte la mejor experiencia de navegación posible. Si sigues usando esta web sin cambiar tus ajustes de cookies o haces clic en «Aceptar», estarás dando tu consentimiento a esto.

    Cerrar