API Fetch

Questo codelab fa parte del corso di formazione Sviluppare app web progressive, sviluppato dal team di formazione di Google Developers. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza.

Per i dettagli completi sul corso, consulta la panoramica sullo sviluppo di app web progressive.

Introduzione

Questo lab ti guida nell'utilizzo dell'API Fetch, un'interfaccia semplice per recuperare le risorse e un miglioramento rispetto all'API XMLHttpRequest.

Obiettivi didattici

  • Come utilizzare l'API Fetch per richiedere risorse
  • Come effettuare richieste GET, HEAD e POST con fetch
  • Come leggere e impostare intestazioni personalizzate
  • Utilizzo e limitazioni di CORS

Che cosa devi sapere

  • JavaScript e HTML di base
  • Familiarità con il concetto e la sintassi di base delle promesse ES2015

Che cosa ti serve

  • Computer con accesso al terminale/alla shell
  • Connessione a internet
  • Un browser che supporti Fetch
  • Un editor di testo
  • Node e npm

Nota: anche se l'API Fetch non è attualmente supportata in tutti i browser, esiste un polyfill.

Scarica o clona il repository pwa-training-labs da GitHub e installa la versione LTS di Node.js, se necessario.

Apri la riga di comando del computer. Vai alla directory fetch-api-lab/app/ e avvia un server di sviluppo locale:

cd fetch-api-lab/app
npm install
node server.js

Puoi terminare il server in qualsiasi momento con Ctrl-c.

Apri il browser e vai all'indirizzo localhost:8081/. Dovresti visualizzare una pagina con i pulsanti per effettuare le richieste (non funzioneranno ancora).

Nota:annulla la registrazione di tutti i service worker e svuota tutte le cache dei service worker per localhost in modo che non interferiscano con il lab. In Chrome DevTools, puoi farlo facendo clic su Cancella dati sito nella sezione Cancella spazio di archiviazione della scheda Applicazione.

Apri la cartella fetch-api-lab/app/ nell'editor di testo che preferisci. La cartella app/ è quella in cui creerai il lab.

Questa cartella contiene:

  • echo-servers/ contiene i file utilizzati per l'esecuzione dei server di test
  • examples/ contiene risorse di esempio che utilizziamo per sperimentare il recupero
  • js/main.js è il codice JavaScript principale dell'app, in cui scriverai tutto il codice
  • index.html è la pagina HTML principale del nostro sito/applicazione di esempio
  • package-lock.json e package.json sono file di configurazione per le dipendenze del nostro server di sviluppo e del server di echo
  • server.js è un server di sviluppo di nodi

L'API Fetch ha un'interfaccia relativamente semplice. Questa sezione spiega come scrivere una richiesta HTTP di base utilizzando fetch.

Recuperare un file JSON

In js/main.js, il pulsante Recupera JSON dell'app è collegato alla funzione fetchJSON.

Aggiorna la funzione fetchJSON per richiedere il file examples/animals.json e registrare la risposta:

function fetchJSON() {
  fetch('examples/animals.json')
    .then(logResult)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera JSON. La console deve registrare la risposta del recupero.

Spiegazione

Il metodo fetch accetta come parametro il percorso della risorsa che vogliamo recuperare, in questo caso examples/animals.json. fetch restituisce una promessa che si risolve in un oggetto Response. Se la promessa viene risolta, la risposta viene passata alla funzione logResult. Se la promessa viene rifiutata, viene eseguito catch e l'errore viene passato alla funzione logError.

Gli oggetti di risposta rappresentano la risposta a una richiesta. Contengono il corpo della risposta, nonché proprietà e metodi utili.

Testare le risposte non valide

Esamina la risposta registrata nella console. Prendi nota dei valori delle proprietà status, url e ok.

Sostituisci la risorsa examples/animals.json in fetchJSON con examples/non-existent.json. La funzione fetchJSON aggiornata ora dovrebbe avere il seguente aspetto:

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(logResult)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai di nuovo clic su Recupera JSON per tentare di recuperare questa risorsa inesistente.

Osserva che il recupero è stato completato correttamente e non ha attivato il blocco catch. Ora trova le proprietà status, URL e ok della nuova risposta.

I valori devono essere diversi per i due file (capisci perché?). Se hai ricevuto errori della console, i valori corrispondono al contesto dell'errore?

Spiegazione

Perché una risposta non riuscita non ha attivato il blocco catch? Questa è una nota importante per recupero e promesse: le risposte errate (come gli errori 404) vengono comunque risolte. Una promessa di recupero viene rifiutata solo se la richiesta non è stata completata, pertanto devi sempre verificare la validità della risposta. Convalideremo le risposte nella sezione successiva.

Per ulteriori informazioni

Controllare la validità della risposta

Dobbiamo aggiornare il nostro codice per verificare la validità delle risposte.

In main.js, aggiungi una funzione per convalidare le risposte:

function validateResponse(response) {
  if (!response.ok) {
    throw Error(response.statusText);
  }
  return response;
}

Quindi, sostituisci fetchJSON con il seguente codice:

function fetchJSON() {
  fetch('examples/non-existent.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera JSON. Controlla la console. Ora la risposta per examples/non-existent.json dovrebbe attivare il blocco catch.

Sostituisci examples/non-existent.json nella funzione fetchJSON con il valore originale examples/animals.json. La funzione aggiornata ora dovrebbe avere il seguente aspetto:

function fetchJSON() {
  fetch('examples/animals.json')
    .then(validateResponse)
    .then(logResult)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera JSON. Dovresti vedere che la risposta viene registrata correttamente come prima.

Spiegazione

Ora che abbiamo aggiunto il controllo validateResponse, le risposte errate (come gli errori 404) generano un errore e viene visualizzato il messaggio catch. Ciò consente di gestire le risposte non riuscite e impedisce la propagazione di risposte impreviste lungo la catena di recupero.

Leggi la risposta.

Le risposte di recupero sono rappresentate come ReadableStreams (specifica degli stream) e devono essere lette per accedere al corpo della risposta. Gli oggetti di risposta hanno metodi per farlo.

In main.js, aggiungi una funzione readResponseAsJSON con il seguente codice:

function readResponseAsJSON(response) {
  return response.json();
}

Quindi, sostituisci la funzione fetchJSON con il seguente codice:

function fetchJSON() {
  fetch('examples/animals.json') // 1
  .then(validateResponse) // 2
  .then(readResponseAsJSON) // 3
  .then(logResult) // 4
  .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera JSON. Controlla la console per verificare che il JSON di examples/animals.json venga registrato (anziché l'oggetto Response).

Spiegazione

Rivediamo cosa sta succedendo.

Passaggio 1: Fetch viene chiamato su una risorsa, examples/animals.json. Fetch restituisce una promessa che si risolve in un oggetto Response. Quando la promessa viene risolta, l'oggetto risposta viene passato a validateResponse.

Passaggio 2: validateResponse verifica se la risposta è valida (è un 200?). In caso contrario, viene generato un errore, vengono ignorati i blocchi then rimanenti e viene attivato il blocco catch. Questo è particolarmente importante. Senza questo controllo, le risposte errate vengono trasmesse a cascata e potrebbero interrompere il codice successivo che potrebbe dipendere dalla ricezione di una risposta valida. Se la risposta è valida, viene passata a readResponseAsJSON.

Passaggio 3: readResponseAsJSON legge il corpo della risposta utilizzando il metodo Response.json(). Questo metodo restituisce una promessa che si risolve in JSON. Una volta risolta questa promessa, i dati JSON vengono passati a logResult. Se la promessa di response.json() viene rifiutata, viene attivato il blocco catch.

Passaggio 4: Infine, i dati JSON della richiesta originale a examples/animals.json vengono registrati da logResult.

Per ulteriori informazioni

Il recupero non è limitato a JSON. In questo esempio recupereremo un'immagine e la aggiungeremo alla pagina.

In main.js, scrivi una funzione showImage con il seguente codice:

function showImage(responseAsBlob) {
  const container = document.getElementById('img-container');
  const imgElem = document.createElement('img');
  container.appendChild(imgElem);
  const imgUrl = URL.createObjectURL(responseAsBlob);
  imgElem.src = imgUrl;
}

Poi aggiungi una funzione readResponseAsBlob che legge le risposte come Blob:

function readResponseAsBlob(response) {
  return response.blob();
}

Aggiorna la funzione fetchImage con il seguente codice:

function fetchImage() {
  fetch('examples/fetching.jpg')
    .then(validateResponse)
    .then(readResponseAsBlob)
    .then(showImage)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera immagine. Dovresti vedere un adorabile cane che riporta un bastone sulla pagina (è un gioco di parole).

Spiegazione

In questo esempio viene recuperata un'immagine, examples/fetching.jpg. Come nell'esercizio precedente, la risposta viene convalidata con validateResponse. La risposta viene quindi letta come Blob (anziché JSON come nella sezione precedente). Viene creato e aggiunto alla pagina un elemento immagine e l'attributo src dell'immagine viene impostato su un URL dati che rappresenta il blob.

Nota:il metodo createObjectURL() dell'oggetto URL viene utilizzato per generare un URL dati che rappresenta il blob. È importante notare questo aspetto. Non puoi impostare l'origine di un'immagine direttamente su un blob. Il blob deve essere convertito in un URL dati.

Per ulteriori informazioni

Questa sezione è una sfida facoltativa.

Aggiorna la funzione fetchText a

  1. recupera /examples/words.txt
  2. convalidare la risposta con validateResponse
  3. leggere la risposta come testo (suggerimento: vedi Response.text())
  4. e visualizzare il testo nella pagina.

Puoi utilizzare questa funzione showText come helper per visualizzare il testo finale:

function showText(responseAsText) {
  const message = document.getElementById('message');
  message.textContent = responseAsText;
}

Salva lo script e aggiorna la pagina. Fai clic su Recupera testo. Se hai implementato correttamente fetchText, dovresti vedere il testo aggiunto nella pagina.

Nota: anche se potrebbe essere allettante recuperare l'HTML e aggiungerlo utilizzando l'attributo innerHTML, fai attenzione. In questo modo, il tuo sito può essere esposto ad attacchi di cross-site scripting.

Per ulteriori informazioni

Per impostazione predefinita, il recupero utilizza il metodo GET, che recupera una risorsa specifica. Tuttavia, il recupero può utilizzare anche altri metodi HTTP.

Esegui una richiesta HEAD

Sostituisci la funzione headRequest con il seguente codice:

function headRequest() {
  fetch('examples/words.txt', {
    method: 'HEAD'
  })
  .then(validateResponse)
  .then(readResponseAsText)
  .then(logResult)
  .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su Richiesta HEAD. Osserva che il contenuto di testo registrato è vuoto.

Spiegazione

Il metodo fetch può ricevere un secondo parametro facoltativo, init. Questo parametro consente la configurazione della richiesta di recupero, ad esempio il metodo di richiesta, la modalità cache, le credenziali, e altro ancora.

In questo esempio, impostiamo il metodo della richiesta di recupero su HEAD utilizzando il parametro init. Le richieste HEAD sono come le richieste GET, tranne per il fatto che il corpo della risposta è vuoto. Questo tipo di richiesta può essere utilizzato quando vuoi solo i metadati di un file, ma non devi trasferire tutti i dati del file.

(Facoltativo) Trovare le dimensioni di una risorsa

Esaminiamo le intestazioni della risposta di recupero per examples/words.txt per determinare le dimensioni del file.

Aggiorna la funzione headRequest per registrare la proprietà content-length della risposta headers (suggerimento: consulta la documentazione delle intestazioni e il metodo get).

Dopo aver aggiornato il codice, salva il file e aggiorna la pagina. Fai clic su Richiesta HEAD. La console deve registrare le dimensioni (in byte) di examples/words.txt.

Spiegazione

In questo esempio, il metodo HEAD viene utilizzato per richiedere le dimensioni (in byte) di una risorsa (rappresentata nell'intestazione content-length) senza caricare effettivamente la risorsa stessa. In pratica, questo può essere utilizzato per determinare se deve essere richiesta la risorsa completa (o anche come richiederla).

Facoltativo: scopri le dimensioni di examples/words.txt utilizzando un altro metodo e verifica che corrispondano al valore dell'intestazione della risposta (puoi cercare come farlo per il tuo sistema operativo specifico. Punti bonus per l'utilizzo della riga di comando).

Per ulteriori informazioni

Fetch può anche inviare dati con richieste POST.

Configurare un server echo

Per questo esempio, devi eseguire un server echo. Dalla directory fetch-api-lab/app/ esegui il seguente comando (se la riga di comando è bloccata dal server localhost:8081, apri una nuova finestra o scheda della riga di comando):

node echo-servers/cors-server.js

Questo comando avvia un semplice server all'indirizzo localhost:5000/ che ripete le richieste inviate.

Puoi terminare questo server in qualsiasi momento con ctrl+c.

Invia una richiesta POST

Sostituisci la funzione postRequest con il seguente codice (assicurati di aver definito la funzione showText della sezione 4 se non l'hai completata):

function postRequest() {
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: 'name=david&message=hello'
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Fai clic su POST request (Richiesta POST). Osserva la richiesta inviata visualizzata sulla pagina. Deve contenere il nome e il messaggio (tieni presente che non riceviamo ancora i dati dal modulo).

Spiegazione

Per inviare una richiesta POST con fetch, utilizziamo il parametro init per specificare il metodo (in modo simile a come abbiamo impostato il metodo HEAD nella sezione precedente). È qui che impostiamo anche il corpo della richiesta, in questo caso una semplice stringa. Il corpo sono i dati che vogliamo inviare.

Nota:in produzione, ricorda di criptare sempre i dati utente sensibili.

Quando i dati vengono inviati come richiesta POST a localhost:5000/, la richiesta viene restituita come risposta. La risposta viene quindi convalidata con validateResponse, letta come testo e visualizzata nella pagina.

In pratica, questo server rappresenterebbe un'API di terze parti.

(Facoltativo) Utilizza l'interfaccia FormData

Puoi utilizzare l'interfaccia FormData per recuperare facilmente i dati dai moduli.

Nella funzione postRequest, crea un'istanza di un nuovo oggetto FormData dall'elemento del modulo msg-form:

const formData = new FormData(document.getElementById('msg-form'));

Poi sostituisci il valore del parametro body con la variabile formData.

Salva lo script e aggiorna la pagina. Compila il modulo (i campi Nome e Messaggio) nella pagina, quindi fai clic su POST. Osserva i contenuti del modulo visualizzati nella pagina.

Spiegazione

Il costruttore FormData può accettare un form HTML e creare un oggetto FormData. Questo oggetto viene compilato con le chiavi e i valori del modulo.

Per ulteriori informazioni

Avvia un server di echo non CORS

Arresta il server echo precedente (premendo ctrl+c dalla riga di comando) e avvia un nuovo server echo dalla directory fetch-lab-api/app/ eseguendo il seguente comando:

node echo-servers/no-cors-server.js

Questo comando configura un altro semplice server echo, questa volta su localhost:5001/. Questo server, tuttavia, non è configurato per accettare richieste multiorigine.

Recuperare dal nuovo server

Ora che il nuovo server è in esecuzione all'indirizzo localhost:5001/, possiamo inviargli una richiesta di recupero.

Aggiorna la funzione postRequest per recuperare i dati da localhost:5001/ anziché da localhost:5000/. Dopo aver aggiornato il codice, salva il file, aggiorna la pagina e fai clic su Richiesta POST.

Nella console dovrebbe essere visualizzato un errore che indica che la richiesta multiorigine è bloccata perché manca l'intestazione CORS Access-Control-Allow-Origin.

Aggiorna fetch nella funzione postRequest con il seguente codice, che utilizza la modalità no-cors (come suggerito dal log degli errori) e rimuove le chiamate a validateResponse e readResponseAsText (vedi spiegazione di seguito):

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5001/', {
    method: 'POST',
    body: formData,
    mode: 'no-cors'
  })
    .then(logResult)
    .catch(logError);
}

Salva lo script e aggiorna la pagina. Quindi, compila il modulo del messaggio e fai clic su INVIA richiesta.

Osserva l'oggetto di risposta registrato nella console.

Spiegazione

Fetch (e XMLHttpRequest) seguono le norme same-origin. Ciò significa che i browser limitano le richieste HTTP multiorigine dagli script. Una richiesta multiorigine si verifica quando un dominio (ad esempio http://foo.com/) richiede una risorsa da un dominio separato (ad esempio http://bar.com/).

Nota:le limitazioni delle richieste multiorigine sono spesso fonte di confusione. Molte risorse come immagini, fogli di stile e script vengono recuperate tra i domini (ovvero tra origini diverse). Tuttavia, si tratta di eccezioni al criterio della stessa origine. Le richieste multiorigine sono ancora limitate da all'interno degli script.

Poiché il server della nostra app ha un numero di porta diverso dai due server echo, le richieste a uno dei due server echo sono considerate cross-origin. Il primo server echo, tuttavia, in esecuzione su localhost:5000/, è configurato per supportare CORS (puoi aprire echo-servers/cors-server.js ed esaminare la configurazione). Il nuovo server di test, in esecuzione su localhost:5001/, non lo è (motivo per cui viene visualizzato un errore).

L'utilizzo di mode: no-cors consente di recuperare una risposta opaca. Ciò consente di ottenere una risposta, ma impedisce l'accesso alla risposta con JavaScript (motivo per cui non possiamo utilizzare validateResponse, readResponseAsText o showResponse). La risposta può comunque essere utilizzata da altre API o memorizzata nella cache da un service worker.

Modifica le intestazioni delle richieste

Fetch supporta anche la modifica delle intestazioni delle richieste. Arresta il server di echo localhost:5001 (senza CORS) e riavvia il server di echo localhost:5000 (CORS) dalla sezione 6:

node echo-servers/cors-server.js

Ripristina la versione precedente della funzione postRequest che recupera i dati da localhost:5000/:

function postRequest() {
  const formData = new FormData(document.getElementById('msg-form'));
  fetch('http://localhost:5000/', {
    method: 'POST',
    body: formData
  })
    .then(validateResponse)
    .then(readResponseAsText)
    .then(showText)
    .catch(logError);
}

Ora utilizza l'interfaccia Header per creare un oggetto Headers all'interno della funzione postRequest chiamata messageHeaders con l'intestazione Content-Type uguale a application/json.

Poi imposta la proprietà headers dell'oggetto init in modo che sia la variabile messageHeaders.

Aggiorna la proprietà body in modo che sia un oggetto JSON convertito in stringa, ad esempio:

JSON.stringify({ lab: 'fetch', status: 'fun' })

Dopo aver aggiornato il codice, salva il file e aggiorna la pagina. Poi fai clic su Richiesta POST.

Nota che la richiesta di echo ora ha un Content-Type di application/json (anziché multipart/form-data come in precedenza).

Ora aggiungi un'intestazione Content-Length personalizzata all'oggetto messageHeaders e assegna alla richiesta una dimensione arbitraria.

Dopo aver aggiornato il codice, salva il file, aggiorna la pagina e fai clic su Richiesta POST. Tieni presente che questa intestazione non viene modificata nella richiesta di echoing.

Spiegazione

L'interfaccia Intestazione consente la creazione e la modifica degli oggetti Intestazioni. Alcune intestazioni, come Content-Type, possono essere modificate dal recupero. Altre, come Content-Length, sono protette e non possono essere modificate (per motivi di sicurezza).

Impostare intestazioni delle richieste personalizzate

Fetch supporta l'impostazione di intestazioni personalizzate.

Rimuovi l'intestazione Content-Length dall'oggetto messageHeaders nella funzione postRequest. Aggiungi l'intestazione personalizzata X-Custom con un valore arbitrario (ad esempio "X-CUSTOM': 'hello world'").

Salva lo script, aggiorna la pagina e poi fai clic su Richiesta POST.

Dovresti vedere che la richiesta di echo ha la proprietà X-Custom che hai aggiunto.

Ora aggiungi un'intestazione Y-Custom all'oggetto Intestazioni. Salva lo script, aggiorna la pagina e fai clic su Richiesta POST.

Nella console dovresti ricevere un errore simile a questo:

Fetch API cannot load http://localhost:5000/. Request header field y-custom is not allowed by Access-Control-Allow-Headers in preflight response.

Spiegazione

Come le richieste multiorigine, le intestazioni personalizzate devono essere supportate dal server da cui viene richiesta la risorsa. In questo esempio, il nostro server di echo è configurato per accettare l'intestazione X-Custom, ma non l'intestazione Y-Custom (puoi aprire echo-servers/cors-server.js e cercare Access-Control-Allow-Headers per verificare di persona). Ogni volta che viene impostata un'intestazione personalizzata, il browser esegue un controllo preflight. Ciò significa che il browser invia prima una richiesta OPTIONS al server per determinare quali metodi e intestazioni HTTP sono consentiti dal server. Se il server è configurato per accettare il metodo e le intestazioni della richiesta originale, questa viene inviata, altrimenti viene generato un errore.

Per ulteriori informazioni

Codice della soluzione

Per ottenere una copia del codice funzionante, vai alla cartella solution.

Ora sai come utilizzare l'API Fetch.

Risorse

Per visualizzare tutti i codelab del corso di formazione sulle PWA, consulta il codelab di benvenuto del corso.