Categorie
Javascript Programmazione

{JS} API di comunicazione: Fetch

Cos’è l’API Fetch?

L’API Fetch rappresenta una valida alternativa ad AJAX che può risultare scomodo e macchinoso.
Fetch ha una sintassi più semplice e meglio integrata che prevede una gestione delle chiamate asincrone basata sulle promise.

Cosa sono le promises

Per comprendere il funzionamento di Fetch introduciamo brevemente il concetto di promise.
Quando scriviamo istruzioni in javascript queste vengono eseguite in sequenze, ossia una riga alla volta.
Ciò vuol dire che ogni riga deve essere interamente eseguita prima di procedere a quella successiva, stiamo parlando di programmazione sincrona.
Nella programmazione asincrona invece si può passare alle righe successive anche se quelle precedenti non hanno terminato l’esecuzione.

Il concetto di promise in javascript è simile a quello di callback. E’ come se nel programma che stiamo compilando inserissimo un segnaposto con la “promessa” di occuparlo il prima possibile con un dato ed in cambio il programma ci dia la possibilità di proseguire con le righe successive.

fetch()

L’Api fornisce un metodo globale “fetch()” che consente in modo semplice e logico di recuperare le risorse in modo asincrono attraverso la rete.

fetch("https://www.cinquepuntozero.it/file.json")
   .then( function(response){
      console.log(response);
   })
   .catch( function(error){
console.log("Si è verificato un errore!")
});

Abbiamo specificato l’URL su cui effettuare la richiesta HTTP come parametro della funzione fetch() ed abbiamo gestito la risposta come una promise

In caso di successo la promise verrà risolta e sarà eseguita la funzione contenuta i then(); in caso contrario sarà eseguita la funzione contenta in catch().

L’oggetto “Response”

Deve essere sottolineato che la promise viene risolta ogni qualvolta c’è una risposta dal server; il che non significa che sia una risposta con codice di stato 200.
A tal proposito vengono in soccorso le proprietà dell’oggetto “response” che rappresenta la risposta del server.

Stato della risposta

response.statusrestituisce il codice di stato HTTP inviato dal server, per esempio 200 in caso di risposta con successo
response.statusTextrestituisce una stringa del codice di stato, che ne descrive testualmente il significato. Ad esempio, se il codice di risposta è 200, la stringa sarà “OK”
response.okÈ un valore booleano che indica se la risposta del server è stata positiva, cioè se il codice di stato restituito è compreso tra 200 e 299, estremi inclusi

Potremmo controllare con esattezza la risposta del server sfruttando le varie proprietà:

fetch("https://www.cinquepuntozero.it/file.json")
.then( function(response){
    if (response.ok) {
       console.log("Contenuto ricevuto");
    }
    if (response.status >= 100 && response.status < 200) {
       console.log("Informazioni per il client");
    }
    if (response.status >= 300 && response.status < 399) {
       console.log("Redirezione");
    }
    if (response.status >= 400 && response.status < 499) {
       console.log("Richiesta errata");
    }
    if (response.status >= 500 && response.status < 599) {
       console.log("Errore sul server");
    }
 })
.catch( function(error){
console.log("Si è verificato un errore!")
});

Leggere le risposte

Sempre l’oggetto “response” mette a disposizione alcuni metodi per ottenere il contenuto restituito dal server:

response.text()restituisce il contenuto sotto forma di testo
response.json()effettua il parsing del contenuto e lo restituisce sotto forma di oggetto
response.blob()restituisce il contenuto sotto forma di dati non strutturati (blob)
response.arrayBuffer()restituisce il contenuto strutturato in un arrayBuffer
In informatica, un binary large object, spesso identificato con l'acronimo BLOB, è un tipo di dato usato nei database.
I campi di tipo BLOB sono destinati alla memorizzazione di dati di grandi dimensioni in formato binario non direttamente interpretabile dal database (come ad esempio immagini grafiche, audio, programmi applicativi o altri oggetti multimediali), e possono contenere fino a diversi gigabyte a seconda del database utilizzato.
fetch("https://www.cinquepuntozero.it/file.json")
  .then( function(response){
    if (response.ok) {
       return utente = response.json();
    }
    })

  .then( function(utente){
       console.log(`Nome: ${utente.nome}
Cognome: ${utente.cognome}
Professione: ${utente.professione}
Numero ID: ${utente.id}`);
    })

  .catch( function(error){
       console.log("Si è verificato un errore!")
    })

Tipi di richieste HTTP

Le richieste al server possono essere di due tipi fondamentalmente; è possibile richiedere:

  • una risorsa statica
  • una risorsa che comprende un elaborazione da parte del server attraverso informazioni passate assieme alla richiesta stessa

Finora abbiamo inviato richieste semplici ed immediate; questo tipo di richieste assumono automaticamente il verbo ed il tipo di richiesta “GET”.
Qualora volessimo effettuare richieste con verbi HTTP differenti, come ad esempio “POST” abbiamo a disposizione due distinti tipi di approccio:

fecth()

Possiamo continuare ad utilizzare il metodo globale “fecth()” ma questa volta dovremo passargli un secondo paramento; il secondo paramento in questione è un oggetto.
Si tratta di un “init object” (“initializer object” tradotto “oggetto inizializzatore”)

fecth("Https://www.cinquepuntozero.it/file.json", {
proprieta: valore,
proprieta: valore,
...
});

init object

L’oggetto “init object” conterrà una serie di informazioni sotto forma di proprietà-valore che contengono le informazioni necessarie per processare la nostra richiesta

fecth("Https://www.cinquepuntozero.it/", {
    method: "post",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
       nome: "Mario",
       cognome: "Rossi"
    })
 });

.then( function(response){
      console.log(response.statusText);
})
.catch( function(error){
console.log("Si è verificato un errore!")
});

Nell’esempio qui sopra stiamo inviando un richiesta di tipo POST esplicitata nella proprietà “method”; abbiamo impostato nella proprietà “headers” un oggetto con una intestazione, necessaria per informare il server su quale tipo di dato stiamo inviando; ed in fine nella proprietà “body” indichiamo l’ oggetto js, da passare mediante la richiesta post, preoccupandoci di convertirlo prima in json.

Le proprietà principali che compongono “init object” sono:

Proprietàvalore
methodcontiene il tipo di richiesta sotto forma di stringa es. “get”, “post” ecc.
headerscontiene un oggetto che definisce l’intestazione della richiesta
bodycontiene il valore da passare nel momento dell’invio della richiesta; può essere una stinga un oggetto ecc.

All’interno delle proprietà “header” e “body” è possibile utilizzare dei costruttori particolari:

fecth("Https://www.cinquepuntozero.it/", {
    method: "post",
    headers: new Headers({ "Content-Type": "multipart/form-data" }),
    body: new FormData(document.getElementById("registrazioneUtente"))
});

.then(...
})
.catch(...
});

I costruttori “new Headers” e “new FormData” sono costruttori utili per impostare efficacemente le nostre richieste HTTP.
Essi contengono ulteriori metodi che ci agevolano nella gestione della richiesta.

new Headers()

Una richiesta al server contiene una serie di informazioni, meta informazioni, che riguardano la richiesta stessa; queste informazioni sono contenute in quella che viene chiamata “intestazione” o “header”.

Quando si conosce il tipo di dato che si richiede, esempio un file.json, è possibile omettere le informazioni contenute nell’header; questo perchè conosciamo già cosa richiederemo e come elaborare la risposta fornita.

Nel caso di una richiesta “post”, dove ci attendiamo una risposta elaborata dal server, è necessario informare il server sul tipo di dato che ci apprestiamo ad inviare; questo perché il server non conosce cosa gli stiamo inviando.

Il costruttore di intestazioni cosi come utilizzato negli esempi precedenti risulta di dubbia utilità, in quanto ci saremmo potuti limitare a passargli l’oggetto “{ “Content-Type”: “multipart/form-data” }”;
in realtà questa classe possiede numerosi metodi per creare una collezione di informazioni da includere nell’header di una richiesta HTTP;
inoltre, ci consente di creare esternamente le intestazione per poi richiamarle.

var mioHeaders = new Headers({
    "Content-Type": "application/json",
    "Accept": "application/json"
 });

Queste informazioni dell’header servono principalmente per il coordinamento tra client e server.
Nell’esempio precedente stiamo includendo nella nostra queste informazioni: la richiesta invia dati json; il browser accetta risposte json.

Una richiesta HTTP avrà un header-della-richiesta ed un header-della-risposta.
Entrambe possono contenere numerose informazioni che elenchiamo nelle tabelle qui sotto:

Header della richiesta
Campo dell’headerFunzioneEsempio
AcceptI tipi di contenuto che il client è in grado di elaborare. Se il campo è vuoto significa che è in grado di elaborare tutti i tipi di contenutoAccept: text/html, application/xml
Accept-CharsetQuale set di caratteri il client è in grado di mostrareAccept-Charset: utf-8
Accept-EncodingI formati compressi supportati dal clientAccept-Encoding: gzip
Accept-LanguageLa lingua desiderataAccept-Language: it-IT
AuthorizationI dati di autentificazione come ad esempio quelli di loginBasic WjbU7D25zTAlV2tZ7==
Cache-ControlLe opzioni del meccanismo di cachingCache-Control: no-cache
CookieI cookie archiviati per questo serverCookie: $Version=1; Content=23
Content-LengthLunghezza del request body (corpo del messaggio)Content-Length: 212
Content-TypeIl tipo MIME del body rilevante per le richieste POST e PUTContent-Type: application/x_222-form-urlencoded
DateData e orario della richiestaDate: Mon, 9 March 2020 09:02:22 GMT
ExpectFormula quanto ci si aspetta dal server, che solitamente coincide con il ricevimento di una richiesta esaustiva.Expect: 100-continue (il server deve inviare Code 100, se è pronto ad accogliere la richiesta)
HostIl nome di dominio del serverHost: esempio.it
If-MatchEsecuzione condizionata di un’azione in base alla corrispondenza del codice inviatoIf-Match: “ft678iujhnjio90’pöl“
If-Modified-SinceInviare solamente se il contenuto richiesto è stato modificato successivamente alla data/ora indicataIF-Modified-Since: Mon 2 Mar 2020 1:00:00 GMT
If-None-MatchCome sopra, ma specificamente per un ETag (Entity-Tag, vedi sotto)If-None-Match: “cxdrt5678iujhgbvb“
If-RangeRichiede solo la parte del contenuto che è stata modificata o che manca dalla cache del clientIf-Range: Mon 2 Mar 2020 1:00:00 GMT
If-Unmodified-SinceIl contrario di IF-Modified-SinceIf-Unmodified-Since: Mon 2 Mar 2020 1:00:00 GMT
Max-ForwardsDefinisce il limite massimo di quanto spesso può essere inoltrata la risposta del serverMax-Forwards: 12
Proxy-AuthorizationViene utilizzato per autenticare il client presso un server proxyProxy-Authorization: Basic WjbU7D25zTAlV2tZ7==
RangeSpecifica che parte del contenuto viene richiestaRange: bytes=0-9999
ReferrerURL della risorsa dalla quale proviene la richiesta (ossia dalla quale è collegata)Referrer: https://esempio.it/index.html
TEAccettazione dell’estensione di codifica di trasferimentoTE: gzip, deflate
User-AgentLo user-agent del client (il browser)Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Header della risposta
Campo dell’headerFunzioneEsempio
Accept-RangesQuali unità del server sono accettate per il range indicato (vedi sopra)Accept-Ranges: bytes
AgeIl tempo in secondi dalla creazione dell’oggetto nella cacheAge: 2300
AllowI tipi di richieste ammessi per una determinata risorsaAllow: GET, POST, HEAD
Cache-ControlSe e per quanto tempo l’oggetto può essere mantenuto nella cacheCache-Control: max-age=4800
ConnectionIl tipo di connessione preferitoConnection: close
Content-EncodingTipo di compressioneContent-Encoding: deflate
Content-LanguageLingua della risorsaContent-Language: it-IT
Content-LengthDimensione del body in bytesContent-Length: 135674
Content-LocationLuogo di archiviazione del file, nel caso in cui provenga da un luogo d’archiviazione diverso da quello richiesto (ad esempio CDN).Content-Location: /esempio.it
Content-Security-PolicyLa politica di sicurezza del serverContent-Security-Policy: frame-src ‘none’; object-src ‘none‘
Content-TypeIl tipo di MIME del file richiestoContent-Type: text/tml; charset=utf-8
DateL’orario della rispostaDate: Mon 2 Mar 2020 1:00:00 GMT
ETagIndica una versione specifica del fileETag: “vt6789oi8uztgfvbn“
ExpiresDa quando il file deve essere considerato superatoExpires: Tue 3 Mar 2020 1:00:00 GMT
Last-ModifiedOrario dell’ultima modifica del fileLast-Modified: Mon 2 Mar 2020 1:00:00 GMT
LocationContraddistingue il luogo d’archiviazione nel quale viene inoltrata la richiestaLocation: https://www.esempio.it
Proxy-AuthenticateIndica se e come il client si deve autenticare con il server proxyProxy-Authenticate: Basic
Retry-AfterA partire da quando il client deve avanzare un’altra richiesta, nel caso in cui la risorsa sia temporaneamente indisponibile (data o secondi)Retry-After: 300
ServerIdentificazione del server.Server: Apache
Set-CookiePosiziona un cookie nel clientSet-Cookie: UserID=XY; Max-Age=3800; Version=1
Transfer-EncodingIl metodo di compressioneTransfer-Encoding: gpzip
VaryInforma quale campo dell’header vada considerato come variabile nel caso in cui un file viene richiesto dalla cacheVary: User-Agent (= il server ha a disposizione diverse versioni del file in base allo User Agent)
ViaIl proxy tramite il quale viene inviata la rispostaVia: 1.1www.esempio.it

I seguenti metodi sono utilizzabili su una nuova istanza della classe Headres:

MetodoDescrizione
mieiHeaders.append(” ” , ” “);aggiunge un header alla collezione;
es. mieiHeaders.append("Content-Type", "application/json");
mieiHeaders.has(“name “);verifica l’esistenza di un header;
es. mieiHeaders.has(“Content-Type”);
mieiHeaders.set(" " , " ");modifica il valore di un header;
es. mieiHeaders.set("Content-Type", "text/plain");
mieiHeaders.get(” “);prende il valore di un header;
es. mieiHeaders.get("Content-Type"); restituisce “text/plain”
mieiHeaders.delete(" ");elimina un header;
es. mieiHeaders.delete("Content-Type");

L’utlizzo all’interno di una richiesta risulta agevolato:

fecth("Https://www.cinquepuntozero.it/", {
    method: "post",
    headers: mieiHeaders,
...
new FormData()

Allo stesso modo per agevolarci nel recupero dei campi input presenti in un form e per passare alla richiesta un oggetto che gli contiene è possibile sfruttare il costruttore di “dati form”;

var mioForm = new FormData();

L’oggetto FormData potrà essere popolato con proprietà/valori corrispondenti al nome ed al valore corrente dei campi input presenti nel form;
Per il recupero sarà sufficiente indicare nel costruttore l’elemento che ha l’id del form stesso:

<form id="registrazioneUtente">
<label>Nome: 
<input type="text" name="nome" value=""></label>
<label>Cognome: 
<input type="text" name="cognome" value=""></label>
</form>
<p id="output"></p>

<script>
var registrazioneUtente = new FormData(document.getElementById('registrazioneUtente');
</script>

Il risultato sarà un oggetto così formato:

{nome:"Mario", cognome: "Rossi"}

Anche in questo caso all’interno della nostra richiesta saremo agevolati:

fecth("Https://www.cinquepuntozero.it/", {
    method: "post",
    headers: mioHeader,
    body: JSON.stringify(registrazioneUtente)
...

I seguenti metodi sono utilizzabili per modificare, l’oggetto generato, istanza della classe FormData:

MetodoDescrizione
registrazioneUtente.append(name, value)aggiunge un campo del form fornendo name e value
registrazioneUtente.delete(name)rimuove il campo fornendo il relativo name
registrazioneUtente.get(name)recupera il valore di un campo fornendo il relativo name
registrazioneUtente.has(name)se esiste il campo con il relativo name ritorna true, altrimenti false
registrazioneUtente.set(name, value)modifica il valore di un campo

Il risultato che otterremo è un recupero automatico dei campi input di un form, la creazione diretta di un oggetto con proprietà e valori dei campi input ed una pulizia all’interno della richiesta HTTP.

Un ultima nota sulla classe FormData: l’oggetto generato non è visibile all’interno della dev-console qualora volessimo visionare o stampare il suo contenuto dovremmo preoccuparci di ciclarlo in questo modo:

for (const [key, value] of registrazioneUtente) {
  output.textContent += `${key}: ${value}\n`;
}
new URLSearchParams()

Il costruttore “URLSearchParams()” ha metodi utili per lavorare con le query String di un url.
Accetta in ingresso un oggetto e converte le coppie proprietà-valori in un queryString manipolabile.

var mioForm = new FormData(document.getElementById('registrazioneUtente');
var queryString = new URLSearchParams(mioForm);
console.log(queryString);

Il risultato sarà il seguente:

nome=Mario&cognome=Rossi

E’ possibile modificare la query manipolando l’oggetto-istanza; elenchiamo le proprietà fondamentali:

MetodoDescrizione
queryString.append()aggiunge una coppia chiave/valore specificata come nuovo parametro di ricerca.
queryString.delete()elimina il parametro di ricerca specificato e il valore associato dall’elenco di tutti i parametri di ricerca.
queryString.toString()restituisce una stringa contenente una stringa di query adatta per l’uso in un URL
queryString.set()imposta il valore associato a un determinato parametro di ricerca sul valore specificato

Elenco completo:
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams

new Request()

All’inizio di questo articolo abbiamo detto che ci sono due possibilità per effettuare una richiesta diversa dalla standard di tipo “get”; abbiamo esaminato la prima che consiste nel passare al metodo globale fecth() un secondo parametro di tipo oggetto “init object”.
Il secondo metodo consente di creare la medesima richiesta attraverso l’oggetto Request() per poi passarlo a fetch():

var richiesta = new Request('https://www.cinquepuntozero.it/', {
method: "post",
header: mioHeader,
body: JSON.stringify(registrazioneUtente)
})

fetch(richiesta).then(function(response){...}.catch(function(error){...}

Le classi “Request”, “Header”, “FormData” e “UrlSearchParams” ci consentono di gestire agevolmente le più comuni richieste e di organizzare al meglio la loro struttura.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *