¿Cómo enviar y recibir datos con la API Fetch?

API Fetch

¿Cómo enviar y recibir datos con la API Fetch?

En un artículo anterior vimos el funcionamiento básico del API Fetch, y en que se diferencia del objeto XMLHttpRequest (XHR).

En dicho artículo nos limitamos a pedir una serie de archivos del servidor Web, concretamente archivos de tipo script. Pero AJAX también se usa para intercambiar información con el servidor. Por ejemplo, cuando estamos escribiendo una búsqueda en Google, se nos van sugiriendo posibles búsquedas; eso también es AJAX.

Existen varias formas de intercambiar información con el servidor, pero una bastante recomendable consiste en usar el formato JSON.

JSON es simplemente un formato de intercambio de información, como lo puedan ser otros como XML o CVS, donde la información se formatea del mismo modo que se escribe un objeto en JavaScript. JSON significa JavaScript Object Notation.

Para poder enviar información al servidor, necesitamos indicar que información queremos enviar, de que forma la queremos enviar, etc.

El método fetch() tiene un segundo parámetro opcional en el que podemos indicar un objeto con varias opciones, entre ellas las que necesitamos para indicar todo esto.

Para el próximo ejemplo vamos a necesitar un archivo PHP llamado media.php que tendrá el siguiente código:

<?php
header("Content-type: application/json; charset=utf-8");
$input = json_decode(file_get_contents("php://input"), true);
$output = array("media" => 0, "html" => "");

$output["html"] .= "<h1>".$input['titulo']."</h1>";
$output["html"] .= "<ul>";
foreach ($input["numeros"] as $value) {
    $output["media"] += $value;
    $output["html"] .= "<li>".$value."</li>";
}
$output["media"] /= count($input["numeros"]);
$output["html"] .= "</ul>";
echo json_encode($output);
?>

Ahora veamos la parte de JavaScript, de nuevo y por simplicidad usaremos Async/Await:

(async () => {
    try {
        // en el objeto “datos” tenemos los datos que vamos a enviar al servidor
        // en este ejemplo tenemos dos datos; un título y un array de números
        var datos = { titulo: "Listado de números", numeros: [2,4,6,8,10] };
        // en el objeto init tenemos los parámetros de la petición AJAX
        var init = {
             // el método de envío de la información será POST
            method: "POST",
            headers: { // cabeceras HTTP
                // vamos a enviar los datos en formato JSON
                'Content-Type': 'application/json'
            },
            // el cuerpo de la petición es una cadena de texto 
            // con los datos en formato JSON
            body: JSON.stringify(datos) // convertimos el objeto a texto
        };
        // realizamos la petición AJAX usando fetch
        // el primer parámetro es el recurso del servidor al que queremos acceder
        // en este ejemplo, es un fichero php llamado media.php que se encuentra
        // dentro de la carpeta ./php y con el código PHP que hay arriba.
        // el segundo parámetro es el objeto init con la información sobre los
        // datos que queremos enviar, el método de envio, etc.
        var response = await fetch('./php/media.php', init);
        if (response.ok) {
            // si la petición se ha resuelto correctamente, 
            // intentamos resolver otra promesa que convierta
            // lo que nos ha respondido el servidor en un objeto de JavaScript.
            // si el servidor no ha enviado correctamente la información
            // en formato JSON, no se podrán convertir correctamente
            // los datos a un objeto, por lo que la promesa fallará
            // y esto provocará un error.
            var respuesta = await response.json();
            // en este ejemplo, el servidor nos devuelve un objeto con dos datos,
            // la media de los números enviados, y un fragmento de HTML
            // con un el título y una lista con los números
            alert("La media es: " + respuesta.media);
            document.write(respuesta.html);
            document.close();
        } else {
            throw new Error(response.statusText);
        }
    } catch (err) {
        console.log("Error al realizar la petición AJAX: " + err.message);
    }
})();

En este ejemplo, el servidor genera la información en formato JSON a partir de la información que envía el navegador del usuario (la lista de números y el título).

Si simplemente queremos acceder a un archivo JSON que ya existe en el servidor,  haremos lo mismo que cuando queríamos solicitar un script del servidor, con la única diferencia de que, para la segunda promesa, en lugar de usar response.text() usaremos response.json() para que nos convierta el texto devuelto por el servidor en un objeto JavaScript.

(async () => {
    try {
        var response = await fetch('./json/comarques.json');
        if (response.ok) {
            var datos = await response.json();
            console.log(datos); // mostramos el objeto en la consola del navegador
            document.write("<pre>"+JSON.stringify(datos, null, 2)+"</pre>");
            document.close(); // y en el documento HTML
        } else {
            throw new Error(response.statusText);
        }
    } catch (err) {
        console.log("Error al realizar la petición AJAX: " + err.message);
    }
})();

Opciones de Fetch

Ya hemos visto que fetch acepta como segundo parámetro un objeto con varias propiedades. Algunas de las más interesantes son las siguientes:

  • body: contenido que se envía en la petición al servidor. Puede ser texto, datos de formulario, datos en formato JSON, etc.
  • headers: aquí podemos indicar una o varias cabeceras HTTP donde podemos indicar información adicional que pueda resultar necesaria para realizar la petición AJAX.
  • cache: establece la forma en la que funcionará la cache del navegador. El valor por defecto suele funcionar bastante bien, pero si queremos asegurarnos de obtener un resultado fresco, podemos usar el valor no-cache. Si no queremos que el resultado se almacene en la cache del navegador, podemos usar no-store.
  • method: indica la acción que queremos realizar sobre el recurso. Por lo general usaremos GET para obtener un recurso y POST para enviar información a un recurso; pero el método adecuado por lo general dependerá del servicio Web o API Web al que estemos accediendo.
  • redirect: existe la posibilidad de que al intentar acceder al recurso el servidor nos devuelva un código de redirección porque el recurso ha cambiado de sitio. En esta propiedad podemos indicar cómo queremos responder ante esta posible situación. Posibles valores son follow si queremos seguir el redireccionamiento, o error si queremos que la promesa sea rechazada
  • credentials: permite indicar si en la petición AJAX queremos incluir datos de autentificación, cookies, etc.
  • mode: permite indicar si se consideran válidas peticiones a otros dominios o no. En caso afirmativo la comunicación deberá de seguir el protocolo CORS (Cross-Origin Resource Sharing). Esto tendrá algunas implicaciones como, por ejemplo, limitaciones en el posible valor de la propiedad method (que solo podrá ser HEAD, GET o POST), en el formato de los datos enviados, etc.

 

¿Cómo enviar los datos de un formulario HTML con la API Fetch?

Veamos ahora un ejemplo de cómo sería el envió de los datos de un formulario mediante fetch.

El formulario HTML podría ser, por ejemplo, uno como el siguiente:

<form action="./php/contactar.php" method="post">
<ul>
  <li>
    <label for="nombre">Nombre:</label>
    <input type="text" id="nombre" name="nombre" required>
  </li>
  <li>
    <label for="asunto">Asunto:</label>
    <input type="text" id="asunto" name="asunto" required>
  </li>
  <li>
    <label for="mensaje">Mensaje:</label>
    <textarea id="mensaje" name="mensaje" cols="30" rows="10" required></textarea>
  </li>
  <li>
    <input type="checkbox" id="priv" name="priv" value="ok" required>
    <label for="priv">
      Acepto las <a href="#">condiciones de privacidad</a>.
    </label>
  </li>
</ul>
<div>
  <input type="submit">
</div>
<div class="form-msg"></div>
</form>

Para recoger y enviar via AJAX los datos del formulario mediante Fetch, podríamos usar un código como el siguiente:

async function enviarFormulario(ev) {
    // el formulario ya no enviará los datos, lo haremos nosotros mediante AJAX
    ev.preventDefault();
    // estamos enviando ya los datos?
    if (enviarFormulario.enviando) { return; }            
    enviarFormulario.enviando = true;
    // obtenemos el formulario y la zona donde mostrar el resultado
    var form = document.querySelector("form");
    var result = document.querySelector(".form-msg");
    result.innerHTML = "Enviando, por favor espera...";
    // obtenemos los datos del formulario
    var datos = new FormData(form);
    // podemos añadir más datos que no se encuentren en el formulario
    datos.append("otro-dato", "valor");
    // preramos la información de envio
    var init = {
        method: form.method,
        body: datos
    };
    // petición ajax con fetch
    try {
        var response = await fetch(form.action, init);
        if (response.ok) {
            // obtenemos la respuesta del servidor web
            // se supone que el servidor nos responderá 
            // si todo ha ido bien o no
            var respuesta = await response.json();
            // asumimos que todo ha ido bien,
            // damos las gracias y limpiamos el formulario
            result.innerHTML = "Gracias por contactar con nosotros.";
            form.reset();
        } else {
            throw new Error(response.statusText);
        }
    } catch (err) {
        result.innerHTML = "Error al enviar el formulario: " + err.message;
    }
    // permitimos volver a enviar el formulario de nuevo
    enviarFormulario.enviando = false;            
}

document.addEventListener("DOMContentLoaded", function() {
    document.querySelector("form").addEventListener("submit", enviarFormulario);
});

Muy similar a lo que hemos visto hasta ahora, con la novedad que en este caso usamos un objeto FormData para obtener todos los datos del formulario. En este ejemplo, hemos asumido que el servidor nos responderá con datos en formato JSON, pero a diferencia del ejemplo anterior, los datos que estamos enviando no se encuentran en este formato.

El objeto Response

Ya hemos visto que cuando hacemos una petición Fetch, existen dos promesas. La primera es la de la propia petición Fetch, y si esta promesa se resuelve correctamente, obtendremos un objeto de la clase Response. A partir de dicho objeto, solemos usar una segunda promesa para obtener el contenido de la respuesta en el formato deseado.

Las propiedades y métodos más destacables de este objeto Response son:

  • .status – contiene el código de estado HTTP de la respuesta. Por ejemplo, 200 si todo ha ido bien, 404 si no se ha encontrado el recurso, etc.
  • .statusText – similar a la propiedad .status pero en este caso el código es una cadena de texto (por ejemplo, para el código 200 el texto es “OK”).
  • .ok – como ya hemos visto, esta propiedad valdrá true si el servidor ha respondido con un código de estado HTTP 2XX, es decir, si la petición AJAX ha sido resuelta satisfactoriamente.
  • .headers – un objeto con las cabeceras HTTP de la respuesta.
  • .redirected – propiedad booleana (true/false) que nos indica si la petición ha sido redireccionada.
  • .url – indica la URL del recurso que nos ha devuelto la respuesta (que por lo general será la misma URL que hemos usado para hacer la petición AJAX, a no ser que se hayan producido redirecciones).
  • .body – es un objeto que nos da acceso a la respuesta del servidor, pero normalmente usaremos alguno de los siguientes métodos que ya nos proporciona el objeto Response.
  • .text() – tal y como ya hemos visto, este método nos devuelve una promesa con el contenido de la respuesta del servidor en formato de texto plano.
  • .json() – de forma similar al anterior, y de nuevo tal y como ya hemos visto, este método nos devuelve una promesa con el contenido en formato JSON.
  • .formData() – nos devuelve una promesa con el contenido en el mismo formato que se usa para enviar los datos de un formulario HTML.
  • .blob() – este método nos devuelve una promesa con el contenido en formato binario; puede ser útil cuando el recurso que estamos pidiendo sea de tipo binario, como por ejemplo, una imagen.
  • .arrayBuffer() – similar al anterior, pero en este caso nos devuelve la información en forma de array.
  • .clone() – nos permite obtener una copia del objeto. Tan pronto consumimos la promesa asociada al objeto Response con un método como .text() o .json() ya no la podemos volver a consumir, con este método, si lo necesitamos, podemos hacer una copia antes de consumirla.

¿Cómo pedir un recurso binario con la API Fetch?

Tal y como acabamos de ver, es posible solicitar a un servidor información en formato binario. Veamos un sencillo ejemplo de como pedir una imagen a un servidor mediante AJAX con Fetch:

Código HTML:

<img id="foto" alt="Cargando imagen...">

Código JavaScript:

(async ()=>{
  // hacemos la petición AJAX
  var peticion = await fetch('img/foto.jpg');
  // resolvemos la segunda promesa 
  // (intentamos convertir la respuesta
  //  del servidor en un objeto binario)
  var datos = await peticion.blob();
  // mostramos la imagen en el elemento
  // img que nos interese
  var foto = document.querySelector("#foto");
  foto.src = URL.createObjectURL(datos);
  foto.alt = "Paisaje";  
})();

Conclusiones

Y con este artículo terminamos esta introducción al API Fetch. Nos encontramos con un API que aunque aún está un poco verde, esta mejor diseñado y es más agradable de usar que el objeto XMLHttpRequest para realizar peticiones AJAX. Sin embargo, no hay que olvidar que el objeto XMLHttpRequest sigue siendo interesante, ya que permite hacer algunas cosas que este nuevo API no es capaz de hacer, como por ejemeplo, llevar un control del progreso de la transferencia de datos entre el navegador y el servidor Web, o la capacidad de cancelar una petición AJAX.

Espero que os haya resultado un tema interesante.

2Comentarios

  • Gabriel
    11/09/2020

    Excelente contenido, es muy util si ya se tiene idea de que es Fetch porque sirve para dejar todo completamente claro, muy buen trabajo Abel.

Escribe un comentario