Promises con javascript

Como vimos en el posteo anterior, Javascript cuenta con diferentes mecanismos para manejar el asincronismo. Uno de los tantos, son las Promises (promesas) y son éstas las que veremos en este articulo.
Una promesa simboliza un valor que puede estar disponible ahora, en el futuro o nunca, es decir que representa la finalización exitosa o fracaso eventual de una determinada operación asincrónica.
Pero, por que deberíamos usarlas?

Supongamos el siguiente ejemplo.
Queremos hacer peticiones Ajax dependientes entre si, la primera de ellas es para obtener los datos de usuarios que tenemos en la aplicación

const xhr = new XMLHttpRequest();
xhr.open("get", "https://jsonplaceholder.typicode.com/users");
xhr.addEventListener("load", function(){
    let usuarios = JSON.parse(xhr.response);
    console.log(usuarios);
});
xhr.send();
 
Una vez que tenemos los datos de los usuarios, podemos quedarnos con su ID y podemos pedir todos los posteos que hizo un determinado usuario
 
const xhr = new XMLHttpRequest()
xhr.open("get", "https://jsonplaceholder.typicode.com/users")
xhr.addEventListener("load", function(){
    let usuarios = JSON.parse(xhr.response)
    console.log(usuarios);
    var xhr_posts = new XMLHttpRequest()
    xhr_posts.open("get" , "https://jsonplaceholder.typicode.com/posts?userId=" + usuarios[0].id)
    xhr_posts.addEventListener("load", function(){
        var posts = JSON.parse(xhr_posts.response)
        console.log(posts);
    })
    xhr_posts.send()
})
xhr.send();
 
Y finalmente, con la información de cada posteo, podemos obtener si algún usuario le hizo un comentario a un posteo determinado
 
const xhr = new XMLHttpRequest()
xhr.open("get", "https://jsonplaceholder.typicode.com/users")
xhr.addEventListener("load", function(){
    let usuarios = JSON.parse(xhr.response)
    console.log(usuarios);
    var xhr_posts = new XMLHttpRequest()
    xhr_posts.open("get" , "https://jsonplaceholder.typicode.com/posts?userId=" + usuarios[0].id)
    xhr_posts.addEventListener("load", function(){
        var posts = JSON.parse(xhr_posts.response)
        console.log(posts);
        var xhr_comentarios = new XMLHttpRequest();
        xhr_comentarios.open("GET", "https://jsonplaceholder.typicode.com/comments?postId=" + posts[ 0 ].id);
        xhr_comentarios.addEventListener("load", function(){
            var comentarios = JSON.parse(xhr_comentarios.response);
            console.log(comentarios);
        })
        xhr_comentarios.send();
    })
    xhr_posts.send()
})
xhr.send();
 

Podemos ver que se genera un triangulo con el formato de nuestro código debido a la dependencia de código asincrónico, ya que una petición depende de la respuesta que se obtiene de una petición anterior y nos puede llegar a confundir para saber en donde estamos ubicados dentro de nuestro programa.
A esto se lo conoce como "Pyramid of doom" o "Callback hell", y lo podemos resolver mediante Promesas.

PROMESAS

Las promesas son objetos que nos permiten escribir código asincrónico de una manera encadenable, es decir, que en lugar de estar pasando los callbacks dentro de las funciones, podemos ir encadenadolos de tal forma que nuestro código se pueda leer como si estuviese escrito secuencialmente.
Veamos un ejemplo para obtener la lista de usuarios con una promesa

const promesaUsuarios = new Promise(function(resolve, reject){
    const xhr = new XMLHttpRequest();
    xhr.open("get", "https://jsonplaceholder.typicode.com/users");
    xhr.addEventListener("load", function(){
        if(xhr.status == 200){
            let usuarios = JSON.parse(xhr.response);
            resolve(usuarios);
        }else{
            reject("No se encontraron usuarios");
        }
    });
    xhr.send();
});

Cuando se crea la Promise, se la pasa un ejecutor, que es una función que generalmente hará un trabajo asincrónico, y que luego una vez completado ejecuta uno de los callbacks que se le pasaron, en el ejemplo fueron nombrados como resolve y reject pero sus nombre puede no ser necesariamente esos.
Estos callbacks al ser llamados, pueden resolver o rechazar la promesa respectivamente.

El objeto Promise tiene un estado con los posibles valores:
- Pending: es el valor inicial, aun la promesa no fue resuelta
- Fulfilled: ocurre cuando la promesa se cumple
- Rejected: ocurre cuando la promesa no se cumple

Cuando una promesa se completa o se rechaza, podemos llamar a sus métodos .then (en caso de que se complete) o .catch (en caso de que se rechace),
estos métodos devuelven una Promesa, y nos permite aprovechar un gran beneficio que tienen, que es ser encadenadas.

Principio de encadenamiento

El encadenamiento nos permite ejecutar dos o mas operaciones asincrónicas seguidas, donde cada operación se inicia con el resultado exitoso de la operación anterior.

promesaUsuarios
    .then(valor => {
        return new Promise(function(resolve, reject){
            const xhr_posts = new XMLHttpRequest();
            xhr_posts.open("get", "https://jsonplaceholder.typicode.com/posts?userId=" + valor[0].id);
            xhr_posts.addEventListener("load", function(){
                if(xhr_posts.status == 200){
                    let posts = JSON.parse(xhr_posts.response);
                    resolve(posts);
                }else{
                    reject("No se encontraron posteos");
                }
            });
            xhr_posts.send();
        })
    })
    .then(valor => {
        return new Promise(function(resolve, reject){
            const xhr_comentarios = new XMLHttpRequest();
            xhr_comentarios.open("get", "https://jsonplaceholder.typicode.com/comments?postId=" + valor[0].id);
            xhr_comentarios.addEventListener("load", function(){
                if(xhr_comentarios.status == 200){
                    let comentarios = JSON.parse(xhr_comentarios.response);
                    resolve(comentarios);
                }else{
                    reject("No se encontraron comentarios");
                }
            });
            xhr_comentarios.send();
        })
    })
    .catch(error => {
        console.error(error);
    });

En el ejemplo podemos ver como mediante consecutivos .then, encadenamos todas las llamadas al API sin necesidad de callbacks y retornando otra Promise que será el punto de entrada para la siguiente llamada al API. De ésta manera rompemos esa forma de pirámide en nuestro código y podemos tenerlo de una manera mas secuencial resultando así un código mas legible y mantenible que los callbacks.
Además, otra ventaja de las promesas, es que Javascript le va a dar una mayor prioridad a las microtareas en el Promise Queue antes que las tareas del Callback Queue (para saber como funciona Javascript te recomiendo leer el siguiente articulo).


FECTH API

La Web API Fetch nos proporciona una interfaz para interactuar con recursos de una manera mas flexible y potente que XMLHttpRequest, ya que trabaja con Promesas en lugar de callbacks y además nos da una definición genérica de los objetos Request y Response.

Veamos el siguiente ejemplo para realizar una petición GET con fetch, tan solo con pasar por por parámetro la URL del recurso a la función fetch, ya podemos realizar la petición y nos devolverá una promesa que se resuelve en un objeto Response a esa petición, ya sea que finalice de manera exitosa o no.
En este caso, una vez que realizamos la petición, vamos a llamar a la función handleJsonRequest que va a ser la encargada de transformarnos la respuesta obtenida al formato json, y luego poder concatenar los .then() que creamos necesarios para realizar nuestra operación.

function pedirUsuarios(){
fetch("https://jsonplaceholder.typicode.com/users")
.then(handleJsonRequest)
.then(usuarios => console.log(usuarios))
.catch(error => console.log(error));
}

function handleJsonRequest(response){
if(!response.ok){
throw new Error("Error de ajax");
}
return response.json();
}

El objeto response representa la respuesta a nuestra petición y cuenta con varias propiedades y métodos para transformar el cuerpo de la respuesta

 
headers Contiene un objeto con Headers asociado
ok Contiene un booleano correspondiente al estado final de la solicitud
status Contiene el codigo HTTP asociado a la respuesta
json() Transforma el body de la respuesta en formato JSON y retorna una promesa
blob() Transforma el body de la respuesta en formato Blob y retorna una promesa

Ademas de una peticion GET, podemos realizar una peticion POST agregando un parametro adicional a fetch(), que es el objeto Request

function agregarUsuario(usuario){
fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(usuario)
})
.then(handleJsonRequest)
.then(json => console.log(json))
.catch(error => console.log(error));
}

En el ejemplo vemos como además de la URL del recurso, agregamos un objeto que contiene un method, headers y un body de la peticion y al igual que
el objeto Responde, Request cuenta con propiedades configurables para realizar el pedido.

method Contiene el metodo de la solicitud
url Contiene la URL de la solicitud
headers Contiene un objeto Headers asociado
body Contiene un objeto Body asociado

 

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!