Skip to content

Latest commit

 

History

History
306 lines (224 loc) · 10.6 KB

File metadata and controls

306 lines (224 loc) · 10.6 KB

Fetch

JavaScript può inviare richieste di rete al server e caricare nuove informazioni ogni volta che è necessario.

Per esempio, possiamo usare le richieste di rete per:

  • Inviare un ordine,
  • Caricare informazioni di un utente,
  • Ricevere gli ultimi aggiornamenti del server,
  • etc...

...e tutto senza alcun ricaricamento della pagina!

Ti sarà capitato di ascoltare o leggere il termine "AJAX" (acronimo di Asynchronous JavaScript And XML) che è comunemente utilizzato per accomunare (sotto un'unica effige) le richieste di rete in JavaScript. Non è però necessario usare XML: il termine proviene da un retaggio del passato ed è per questo che fa parte dell'abbreviazione.

Ci sono molti modi per inviare richieste di rete per richiedere informazioni dal server.

Il metodo fetch() è tra tutti il più moderno e versatile, e per questo inizieremo ad analizzare proprio questo. Questo metodo non è supportato dai browser più datati (ma è possibile risolvere con dei polyfills), ma lo è ampiamente tra quelli recenti.

La sintassi base è:

let promise = fetch(url, [options])
  • url -- l'URL da raggiungere.
  • options -- parametri opzionali: metodi, headers etc.

Senza options, questa è una semplice richiesta GET che scarica il contenuto di url.

Ottenere una risposta è comunemente un processo che si svolge in due fasi.

Possiamo valutare gli status HTTP dalle proprietà:

  • status -- HTTP status code, ad esempio 200.
  • ok -- boolean, true se l'HTTP status code è 200-299.

Per esempio:

let response = await fetch(url);

if (response.ok) { // se l'HTTP-status è 200-299
  // ricevi il body della risposta (il metodo sarà spiegato di seguito)
  let json = await response.json();
} else {
  alert("HTTP-Error: " + response.status);
}

Seconda fase: per prelevare il body della risposta, abbiamo bisogno di un ulteriore metodo.

Response fornisce molteplici metodi promise-based per accedere al body in svariati formati:

  • response.text() -- legge il la risposta e ritorna un testo,
  • response.json() -- interpreta e ritorna la risposta come un JSON,
  • response.formData() -- ritorna la risposta come un oggetto (object) FormData (spiegato nel prossimo capitolo),
  • response.blob() -- ritorna la risposta come Blob (binary data con type),
  • response.arrayBuffer() -- ritorna la risposta come ArrayBuffer (rappresentazione low-level di binary data),
  • inoltre, response.body è un oggetto (object) ReadableStream, che consente di leggere il body "pezzo per pezzo" (chunk-by-chunk), come vedremo dopo in un esempio.

Ad esempio, otteniamo un oggetto (object) JSON con gli ultimi commit da GitHub:

let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);

*!*
let commits = await response.json(); // legge il body della risposta e lo interpreta come JSON
*/!*

alert(commits[0].author.login);

O facciamo lo stesso senza await, utilizzando la sintassi canonica delle promises:

fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
  .then(response => response.json())
  .then(commits => alert(commits[0].author.login));

Per ottenere il testo della risposta, await response.text() invece del .json():

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

let text = await response.text(); // legge il body della risposta come testo

alert(text.slice(0, 80) + '...');

Come caso d'uso per la lettura del binary format, richiediamo e mostriamo l'immagine del logo delle specifiche "fetch" (vedi il capitolo Blob per i dettagli sulle possibilità offerte dai Blob):

let response = await fetch('/article/fetch/logo-fetch.svg');

*!*
let blob = await response.blob(); // download del Blob object
*/!*

// crea un tag <img>
let img = document.createElement('img');
img.style = 'position:fixed;top:10px;left:10px;width:100px';
document.body.append(img);

// mostra il logo
img.src = URL.createObjectURL(blob);

setTimeout(() => { // nascondi dopo tre secondi
  img.remove();
  URL.revokeObjectURL(img.src);
}, 3000);
Possiamo solo scegliere un metodo di lettura del body.

Se per esempio abbiamo già prelevato il response con `response.text()`, successivamente `response.json()` non funzionerà, dato che il body sarà stato già processato.

```js
let text = await response.text(); // elaborazione del response body
let parsed = await response.json(); // fallisce (già elaborato)

Headers della risposta (o response headers)

Le response headers sono disponibili nell'oggetto (object) Map-like response.headers.

In realtà non è esattamente un oggetto (object) Map, ma ha metodi molto simili per ottenere le singole header per nome o iterare tra tutte le headers:

let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');

// ricevo un header
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8

// itero tra tutte le headers
for (let [key, value] of response.headers) {
  alert(`${key} = ${value}`);
}

Headers della richiesta (o request headers)

Per settare un header della request in fetch, possiamo usare la chiave headers dell'oggetto (object) passato come parametro delle opzioni, come ad esempio:

let response = fetch(protectedUrl, {
  headers: {
    Authentication: 'secret'
  }
});

...ci sono però una serie di HTTP headers proibiti, che non siamo autorizzati a settare:

  • Accept-Charset, Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie, Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • Proxy-*
  • Sec-*

Queste headers sono controllate esclusivamente dal browser perché aiutano a garantire una comunicazione HTTP corretta e sicura.

Richieste POST (o POST requests)

Per eseguire una richiesta POST o una richiesta con un altro metodo, possiamo usare le opzioni di fetch:

  • method -- metodo HTTP, es. POST,
  • body -- il body della richiesta, scegliendo tra:
    • una stringa (string) (es. JSON-encoded),
    • oggetto (object) FormData, per inviare i dati come form/multipart,
    • Blob/BufferSource per inviare binary data,
    • URLSearchParams, per inviare i dati in x-www-form-urlencoded encoding, anche se raramente utilizzato.

Il formato più comunemente utilizzato è il JSON.

Per esempio, il codice seguente invia l'oggetto (object) user come JSON:

let user = {
  name: 'John',
  surname: 'Smith'
};

*!*
let response = await fetch('/article/fetch/post/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: JSON.stringify(user)
});
*/!*

let result = await response.json();
alert(result.message);

Nota che, se il body della richiesta è una stringa (string), la Content-Type header è settata di default a text/plain;charset=UTF-8.

Se stiamo invece inviando un JSON, usiamo application/json come Content-Type corretto per i dati nelle opzioni headers.

Inviare un'immagine

Possiamo anche inviare binary data con fetch usando oggetti Blob o BufferSource.

In questo esempio, c'è un <canvas> sul quale possiamo disegnare spostarci sopra con il mouse. Al clic sul bottone "Invia" invieremo l'immagine al server:

<body style="margin:0">
  <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas>

  <input type="button" value="Invia" onclick="submit()">

  <script>
    canvasElem.onmousemove = function(e) {
      let ctx = canvasElem.getContext('2d');
      ctx.lineTo(e.clientX, e.clientY);
      ctx.stroke();
    };

    async function submit() {
      let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
      let response = await fetch('/article/fetch/post/image', {
        method: 'POST',
        body: blob
      });

      // il server risponde con la conferma e la dimensione dell'immagine
      let result = await response.json();
      alert(result.message);
    }

  </script>
</body>

Nota che in questa occasione, invece, non impostiamo manualmente l'header Content-Type, perché un oggetto (object) Blob ha un tipo incorporato (in questo caso image/png, generato da toBlob). Per gli oggetti Blob, il tipo generato diventa il valore di Content-Type.

La funzione submit() può essere riscritta senza async/await come ad esempio:

function submit() {
  canvasElem.toBlob(function(blob) {        
    fetch('/article/fetch/post/image', {
      method: 'POST',
      body: blob
    })
      .then(response => response.json())
      .then(result => alert(JSON.stringify(result, null, 2)))
  }, 'image/png');
}

Riepilogo

Una tipica richiesta fetch consiste in 2 chiamate await:

let response = await fetch(url, options); // ritorna le response headers
let result = await response.json(); // legge il body come JSON

O la versione senza await:

fetch(url, options)
  .then(response => response.json())
  .then(result => /* processa qui il result */)

Proprietà del response:

  • response.status -- codice HTTP della risposta,
  • response.ok -- true se lo status è 200-299.
  • response.headers -- oggetto (object) Map-like con le HTTP headers.

Metodi per ricevere il response body:

  • response.text() -- ritorna la risposta come testo,
  • response.json() -- ritorna ed interpreta la risposta come oggetto (object) JSON,
  • response.formData() -- ritorna la risposta come oggetto (object) FormData (per il form/multipart encoding, vedi il prossimo capitolo),
  • response.blob() -- ritorna la risposta come oggetto (object) Blob (binary data con type),
  • response.arrayBuffer() -- ritorna la risposta come oggetto (object) ArrayBuffer (low-level binary data),

Altre opzioni di fetch:

  • method -- metodo HTTP,
  • headers -- un oggetto (object) con le headers della richiesta (non tutte le headers sono concesse),
  • body -- i dati da inviare (request body) come string o come oggetti FormData, BufferSource, Blob, UrlSearchParams.

Nei prossimi capitoli vedremo ulteriori opzioni e casi d'uso di fetch.