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 testexamples/
contiene risorse di esempio che utilizziamo per sperimentare il recuperojs/main.js
è il codice JavaScript principale dell'app, in cui scriverai tutto il codiceindex.html
è la pagina HTML principale del nostro sito/applicazione di esempiopackage-lock.json
epackage.json
sono file di configurazione per le dipendenze del nostro server di sviluppo e del server di echoserver.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
- recupera
/examples/words.txt
- convalidare la risposta con
validateResponse
- leggere la risposta come testo (suggerimento: vedi Response.text())
- 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.