Cet atelier de programmation fait partie du cours de formation "Développer des progressive web apps", développé par l'équipe de formation Google Developers. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre.
Pour en savoir plus sur le cours, consultez la présentation du développement de progressive web apps.
Introduction
Cet atelier vous explique comment utiliser l'API Fetch, une interface simple pour récupérer des ressources, qui est une amélioration de l'API XMLHttpRequest.
Points abordés
- Utiliser l'API Fetch pour demander des ressources
- Envoyer des requêtes GET, HEAD et POST avec fetch
- Lire et définir des en-têtes personnalisés
- Utilisation et limites du CORS
À savoir
- Connaissances de base en JavaScript et HTML
- Vous maîtrisez le concept et la syntaxe de base des Promesses ES2015.
Ce dont vous avez besoin
- Un ordinateur avec accès au terminal/shell
- Connexion à Internet
- Navigateur compatible avec Fetch
- Un éditeur de texte
- Node et npm
Remarque : Bien que l'API Fetch ne soit pas actuellement compatible avec tous les navigateurs, il existe un polyfill.
Téléchargez ou clonez le dépôt pwa-training-labs depuis GitHub et installez la version LTS de Node.js, si nécessaire.
Ouvrez la ligne de commande de votre ordinateur. Accédez au répertoire fetch-api-lab/app/
et démarrez un serveur de développement local :
cd fetch-api-lab/app npm install node server.js
Vous pouvez arrêter le serveur à tout moment avec Ctrl-c
.
Ouvrez votre navigateur et accédez à localhost:8081/
. Vous devriez voir une page avec des boutons permettant d'envoyer des requêtes (ils ne fonctionneront pas encore).
Remarque : Annulez l'enregistrement de tous les service workers et effacez tous les caches de service workers pour localhost afin qu'ils n'interfèrent pas avec l'atelier. Dans les outils pour les développeurs Chrome, vous pouvez y parvenir en cliquant sur Effacer les données du site dans la section Effacer le stockage de l'onglet Application.
Ouvrez le dossier fetch-api-lab/app/
dans l'éditeur de texte de votre choix. Le dossier app/
est celui dans lequel vous allez créer l'atelier.
Ce dossier contient :
echo-servers/
contient les fichiers utilisés pour exécuter les serveurs de test.examples/
contient des exemples de ressources que nous utilisons pour tester la récupération.js/main.js
est le fichier JavaScript principal de l'application. C'est là que vous écrirez tout votre code.index.html
est la page HTML principale de notre exemple de site/application.package-lock.json
etpackage.json
sont des fichiers de configuration pour nos dépendances de serveur de développement et de serveur d'écho.server.js
est un serveur de développement de nœuds
L'API Fetch dispose d'une interface relativement simple. Cette section explique comment écrire une requête HTTP de base à l'aide de fetch.
Récupérer un fichier JSON
Dans js/main.js
, le bouton Fetch JSON (Récupérer le JSON) de l'application est associé à la fonction fetchJSON
.
Mettez à jour la fonction fetchJSON
pour demander le fichier examples/animals.json
et consigner la réponse :
function fetchJSON() {
fetch('examples/animals.json')
.then(logResult)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer le fichier JSON. La console doit enregistrer la réponse de récupération.
Explication
La méthode fetch
accepte le chemin d'accès à la ressource que nous souhaitons récupérer en tant que paramètre, en l'occurrence examples/animals.json
. fetch
renvoie une promesse qui se résout en un objet Response. Si la promesse est résolue, la réponse est transmise à la fonction logResult
. Si la promesse est rejetée, catch
prend le relais et l'erreur est transmise à la fonction logError
.
Les objets de réponse représentent la réponse à une requête. Ils contiennent le corps de la réponse, ainsi que des propriétés et des méthodes utiles.
Tester les réponses non valides
Examinez la réponse enregistrée dans la console. Notez les valeurs des propriétés status
, url
et ok
.
Remplacez la ressource examples/animals.json
dans fetchJSON
par examples/non-existent.json
. La fonction fetchJSON
mise à jour devrait désormais se présenter comme suit :
function fetchJSON() {
fetch('examples/non-existent.json')
.then(logResult)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez à nouveau sur Récupérer le JSON pour essayer de récupérer cette ressource inexistante.
Notez que la récupération s'est terminée avec succès et n'a pas déclenché le bloc catch
. Recherchez maintenant les propriétés status
, URL
et ok
de la nouvelle réponse.
Les valeurs doivent être différentes pour les deux fichiers (comprenez-vous pourquoi ?). Si vous avez reçu des erreurs de console, les valeurs correspondent-elles au contexte de l'erreur ?
Explication
Pourquoi une réponse ayant échoué n'a-t-elle pas activé le bloc catch
? Remarque importante concernant les récupérations et les promesses : les mauvaises réponses (comme les erreurs 404) sont toujours résolues. Une promesse de récupération n'est rejetée que si la requête n'a pas pu être exécutée. Vous devez donc toujours vérifier la validité de la réponse. Nous validerons les réponses dans la section suivante.
Pour en savoir plus
Vérifier la validité de la réponse
Nous devons mettre à jour notre code pour vérifier la validité des réponses.
Dans main.js
, ajoutez une fonction pour valider les réponses :
function validateResponse(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
Remplacez ensuite fetchJSON
par le code suivant :
function fetchJSON() {
fetch('examples/non-existent.json')
.then(validateResponse)
.then(logResult)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer le fichier JSON. Consultez la console. La réponse pour examples/non-existent.json
devrait maintenant déclencher le bloc catch
.
Remplacez examples/non-existent.json
dans la fonction fetchJSON
par le examples/animals.json
d'origine. La fonction mise à jour devrait désormais se présenter comme suit :
function fetchJSON() {
fetch('examples/animals.json')
.then(validateResponse)
.then(logResult)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer le fichier JSON. Vous devriez constater que la réponse est bien enregistrée, comme auparavant.
Explication
Maintenant que nous avons ajouté la vérification validateResponse
, les mauvaises réponses (comme les erreurs 404) génèrent une erreur et le catch
prend le relais. Cela nous permet de gérer les réponses ayant échoué et d'empêcher les réponses inattendues de se propager dans la chaîne de récupération.
Lire la réponse
Les réponses Fetch sont représentées sous la forme de ReadableStreams (spécification des flux) et doivent être lues pour accéder au corps de la réponse. Les objets de réponse disposent de méthodes pour ce faire.
Dans main.js
, ajoutez une fonction readResponseAsJSON
avec le code suivant :
function readResponseAsJSON(response) {
return response.json();
}
Remplacez ensuite la fonction fetchJSON
par le code suivant :
function fetchJSON() {
fetch('examples/animals.json') // 1
.then(validateResponse) // 2
.then(readResponseAsJSON) // 3
.then(logResult) // 4
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer le fichier JSON. Vérifiez dans la console que le JSON de examples/animals.json
est enregistré (au lieu de l'objet Response).
Explication
Examinons la situation.
Étape 1 : Fetch est appelé sur une ressource, examples/animals.json
. Fetch renvoie une promesse qui se résout en un objet Response. Lorsque la promesse est résolue, l'objet de réponse est transmis à validateResponse
.
Étape 2 : validateResponse
vérifie si la réponse est valide (code 200). Si ce n'est pas le cas, une erreur est générée, ce qui permet d'ignorer le reste des blocs then
et de déclencher le bloc catch
. C'est particulièrement important. Sans cette vérification, les mauvaises réponses sont transmises à la chaîne et pourraient casser le code ultérieur qui peut dépendre de la réception d'une réponse valide. Si la réponse est valide, elle est transmise à readResponseAsJSON
.
Étape 3 : readResponseAsJSON
lit le corps de la réponse à l'aide de la méthode Response.json(). Cette méthode renvoie une promesse qui se résout en JSON. Une fois cette promesse résolue, les données JSON sont transmises à logResult
. (Si la promesse de response.json()
est rejetée, le bloc catch
est déclenché.)
Étape 4 : Enfin, les données JSON de la requête d'origine adressée à examples/animals.json
sont enregistrées par logResult
.
Pour en savoir plus
Fetch ne se limite pas à JSON. Dans cet exemple, nous allons récupérer une image et l'ajouter à la page.
Dans main.js
, écrivez une fonction showImage
avec le code suivant :
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;
}
Ajoutez ensuite une fonction readResponseAsBlob
qui lit les réponses en tant que Blob :
function readResponseAsBlob(response) {
return response.blob();
}
Modifiez la fonction fetchImage
avec le code suivant :
function fetchImage() {
fetch('examples/fetching.jpg')
.then(validateResponse)
.then(readResponseAsBlob)
.then(showImage)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer l'image. Vous devriez voir un adorable chien chercher un bâton sur la page (c'est une blague sur la récupération de données !).
Explication
Dans cet exemple, une image est récupérée, examples/fetching.jpg
. Comme dans l'exercice précédent, la réponse est validée avec validateResponse
. La réponse est ensuite lue en tant que Blob (au lieu de JSON comme dans la section précédente). Un élément d'image est créé et ajouté à la page, et l'attribut src
de l'image est défini sur une URL de données représentant le Blob.
Remarque : La méthode createObjectURL()
de l'objet URL est utilisée pour générer une URL de données représentant le Blob. Il est important de le noter. Vous ne pouvez pas définir directement la source d'une image sur un blob. Le Blob doit être converti en URL de données.
Pour en savoir plus
Cette section est un défi facultatif.
Mettez à jour la fonction fetchText
pour
- fetch
/examples/words.txt
- valider la réponse avec
validateResponse
- lire la réponse sous forme de texte (indice : voir Response.text())
- et afficher le texte sur la page.
Vous pouvez utiliser cette fonction showText
comme aide pour afficher le texte final :
function showText(responseAsText) {
const message = document.getElementById('message');
message.textContent = responseAsText;
}
Enregistrez le script et actualisez la page. Cliquez sur Récupérer le texte. Si vous avez correctement implémenté fetchText
, du texte devrait s'afficher sur la page.
Remarque : Bien qu'il puisse être tentant de récupérer du code HTML et de l'ajouter à l'aide de l'attribut innerHTML
, soyez prudent. Cela peut exposer votre site à des attaques de script intersites.
Pour en savoir plus
Par défaut, la récupération utilise la méthode GET, qui récupère une ressource spécifique. Toutefois, la récupération peut également utiliser d'autres méthodes HTTP.
Effectuer une requête HEAD
Remplacez la fonction headRequest
par le code suivant :
function headRequest() {
fetch('examples/words.txt', {
method: 'HEAD'
})
.then(validateResponse)
.then(readResponseAsText)
.then(logResult)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Requête HEAD. Notez que le contenu textuel enregistré est vide.
Explication
La méthode fetch
peut recevoir un deuxième paramètre facultatif, init
. Ce paramètre permet de configurer la requête d'extraction, comme la méthode de requête, le mode cache, les identifiants, et plus encore.
Dans cet exemple, nous définissons la méthode de requête fetch sur HEAD à l'aide du paramètre init
. Les requêtes HEAD sont identiques aux requêtes GET, sauf que le corps de la réponse est vide. Ce type de requête peut être utilisé lorsque vous souhaitez uniquement obtenir des métadonnées sur un fichier, mais que vous n'avez pas besoin de transporter toutes les données du fichier.
Facultatif : Trouver la taille d'une ressource
Examinons les en-têtes de la réponse de récupération pour examples/words.txt
afin de déterminer la taille du fichier.
Mettez à jour la fonction headRequest
pour enregistrer la propriété content-length
de la réponse headers
(conseil : consultez la documentation sur les en-têtes et la méthode get).
Une fois le code modifié, enregistrez le fichier et actualisez la page. Cliquez sur Requête HEAD. La console doit enregistrer la taille (en octets) de examples/words.txt
.
Explication
Dans cet exemple, la méthode HEAD est utilisée pour demander la taille (en octets) d'une ressource (représentée dans l'en-tête content-length
) sans charger la ressource elle-même. En pratique, cela peut être utilisé pour déterminer si la ressource complète doit être demandée (ou même comment la demander).
Facultatif : Déterminez la taille de examples/words.txt
à l'aide d'une autre méthode et vérifiez qu'elle correspond à la valeur de l'en-tête de réponse (vous pouvez rechercher comment procéder pour votre système d'exploitation spécifique. Bonus si vous utilisez la ligne de commande !).
Pour en savoir plus
Fetch peut également envoyer des données avec des requêtes POST.
Configurer un serveur d'écho
Pour cet exemple, vous devez exécuter un serveur d'écho. À partir du répertoire fetch-api-lab/app/
, exécutez la commande suivante (si votre ligne de commande est bloquée par le serveur localhost:8081
, ouvrez une nouvelle fenêtre ou un nouvel onglet de ligne de commande) :
node echo-servers/cors-server.js
Cette commande démarre un serveur simple sur localhost:5000/
qui renvoie les requêtes qui lui sont envoyées.
Vous pouvez arrêter ce serveur à tout moment avec ctrl+c
.
Envoyer une requête POST
Remplacez la fonction postRequest
par le code suivant (assurez-vous d'avoir défini la fonction showText
de la section 4 si vous ne l'avez pas fait) :
function postRequest() {
fetch('http://localhost:5000/', {
method: 'POST',
body: 'name=david&message=hello'
})
.then(validateResponse)
.then(readResponseAsText)
.then(showText)
.catch(logError);
}
Enregistrez le script et actualisez la page. Cliquez sur Requête POST. Observez la demande envoyée qui s'affiche sur la page. Il doit contenir le nom et le message (notez que nous n'obtenons pas encore de données du formulaire).
Explication
Pour envoyer une requête POST avec fetch, nous utilisons le paramètre init
pour spécifier la méthode (comme nous l'avons fait pour la méthode HEAD dans la section précédente). C'est également ici que nous définissons le corps de la requête, qui est une simple chaîne dans ce cas. Le corps correspond aux données que nous souhaitons envoyer.
Remarque : En production, n'oubliez pas de toujours chiffrer les données utilisateur sensibles.
Lorsque des données sont envoyées sous forme de requête POST à localhost:5000/
, la requête est renvoyée en tant que réponse. La réponse est ensuite validée avec validateResponse
, lue sous forme de texte et affichée sur la page.
En pratique, ce serveur représenterait une API tierce.
Facultatif : Utiliser l'interface FormData
Vous pouvez utiliser l'interface FormData pour récupérer facilement les données des formulaires.
Dans la fonction postRequest
, instanciez un nouvel objet FormData
à partir de l'élément de formulaire msg-form
:
const formData = new FormData(document.getElementById('msg-form'));
Remplacez ensuite la valeur du paramètre body
par la variable formData
.
Enregistrez le script et actualisez la page. Remplissez le formulaire (champs Name et Message) sur la page, puis cliquez sur la requête POST. Observez le contenu du formulaire affiché sur la page.
Explication
Le constructeur FormData
peut accepter un form
HTML et créer un objet FormData
. Cet objet est renseigné avec les clés et les valeurs du formulaire.
Pour en savoir plus
Démarrer un serveur d'écho non CORS
Arrêtez le serveur d'écho précédent (en appuyant sur ctrl+c
depuis la ligne de commande) et démarrez un nouveau serveur d'écho depuis le répertoire fetch-lab-api/app/
en exécutant la commande suivante :
node echo-servers/no-cors-server.js
Cette commande configure un autre serveur d'écho simple, cette fois à l'adresse localhost:5001/
. Toutefois, ce serveur n'est pas configuré pour accepter les requêtes d'origine croisée.
Récupérer les données depuis le nouveau serveur
Maintenant que le nouveau serveur s'exécute sur localhost:5001/
, nous pouvons lui envoyer une requête fetch.
Mettez à jour la fonction postRequest
pour qu'elle récupère les données à partir de localhost:5001/
au lieu de localhost:5000/
. Une fois le code mis à jour, enregistrez le fichier, actualisez la page, puis cliquez sur Requête POST.
Une erreur devrait s'afficher dans la console, indiquant que la requête cross-origin est bloquée, car l'en-tête CORS Access-Control-Allow-Origin
est manquant.
Mettez à jour le fetch
dans la fonction postRequest
avec le code suivant, qui utilise le mode no-cors (comme le suggère le journal des erreurs) et supprime les appels à validateResponse
et readResponseAsText
(voir l'explication ci-dessous) :
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);
}
Enregistrez le script et actualisez la page. Remplissez ensuite le formulaire de message et cliquez sur POST Request (Requête POST).
Observez l'objet de réponse consigné dans la console.
Explication
Fetch (et XMLHttpRequest) suivent la règle d'origine identique. Cela signifie que les navigateurs limitent les requêtes HTTP d'origine croisée à partir de scripts. Une requête inter-origines se produit lorsqu'un domaine (par exemple, http://foo.com/
) demande une ressource à un autre domaine (par exemple, http://bar.com/
).
Remarque : Les restrictions concernant les requêtes d'origine croisée sont souvent source de confusion. De nombreuses ressources, telles que des images, des feuilles de style et des scripts, sont récupérées sur plusieurs domaines (c'est-à-dire d'origine croisée). Toutefois, il s'agit d'exceptions à la règle d'origine commune. Les requêtes d'origine croisée sont toujours limitées dans les scripts.
Étant donné que le serveur de notre application possède un numéro de port différent de celui des deux serveurs d'écho, les requêtes adressées à l'un ou l'autre des serveurs d'écho sont considérées comme des requêtes d'origine croisée. Toutefois, le premier serveur d'écho, qui s'exécute sur localhost:5000/
, est configuré pour prendre en charge CORS (vous pouvez ouvrir echo-servers/cors-server.js
et examiner la configuration). Le nouveau serveur d'écho, qui s'exécute sur localhost:5001/
, ne l'est pas (c'est pourquoi nous obtenons une erreur).
L'utilisation de mode: no-cors
permet de récupérer une réponse opaque. Cela nous permet d'obtenir une réponse, mais empêche l'accès à la réponse avec JavaScript (c'est pourquoi nous ne pouvons pas utiliser validateResponse
, readResponseAsText
ou showResponse
). La réponse peut toujours être consommée par d'autres API ou mise en cache par un service worker.
Modifier les en-têtes de requête
Fetch permet également de modifier les en-têtes de requête. Arrêtez le serveur d'écho localhost:5001
(sans CORS) et redémarrez le serveur d'écho localhost:5000
(CORS) de la section 6 :
node echo-servers/cors-server.js
Restaurez la version précédente de la fonction postRequest
qui récupère les données à partir de 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);
}
Utilisez maintenant l'interface Header pour créer un objet Headers à l'intérieur de la fonction postRequest
appelée messageHeaders
avec l'en-tête Content-Type
égal à application/json
.
Définissez ensuite la propriété headers
de l'objet init
sur la variable messageHeaders
.
Mettez à jour la propriété body
pour qu'elle soit un objet JSON sous forme de chaîne, par exemple :
JSON.stringify({ lab: 'fetch', status: 'fun' })
Une fois le code modifié, enregistrez le fichier et actualisez la page. Cliquez ensuite sur Requête POST.
Notez que la requête renvoyée a maintenant un Content-Type
de application/json
(au lieu de multipart/form-data
comme précédemment).
Ajoutez maintenant un en-tête Content-Length
personnalisé à l'objet messageHeaders
et attribuez une taille arbitraire à la requête.
Une fois le code mis à jour, enregistrez le fichier, actualisez la page, puis cliquez sur POST Request (Requête POST). Notez que cet en-tête n'est pas modifié dans la requête renvoyée.
Explication
L'interface d'en-tête permet de créer et de modifier des objets Headers. Certains en-têtes, comme Content-Type
, peuvent être modifiés par la récupération. D'autres, comme Content-Length
, sont protégés et ne peuvent pas être modifiés (pour des raisons de sécurité).
Définir des en-têtes de requête personnalisés
Fetch permet de définir des en-têtes personnalisés.
Supprimez l'en-tête Content-Length
de l'objet messageHeaders
dans la fonction postRequest
. Ajoutez l 'en-tête personnalisé X-Custom
avec une valeur arbitraire (par exemple, X-CUSTOM': 'hello world'
).
Enregistrez le script, actualisez la page, puis cliquez sur POST Request (Demande POST).
Vous devriez voir que la requête renvoyée contient la propriété X-Custom
que vous avez ajoutée.
Ajoutez maintenant un en-tête Y-Custom
à l'objet Headers. Enregistrez le script, actualisez la page, puis cliquez sur POST Request (Requête POST).
Vous devriez obtenir une erreur semblable à celle-ci dans la console :
Fetch API cannot load http://localhost:5000/. Request header field y-custom is not allowed by Access-Control-Allow-Headers in preflight response.
Explication
Comme pour les requêtes inter-origines, les en-têtes personnalisés doivent être acceptés par le serveur à partir duquel la ressource est demandée. Dans cet exemple, notre serveur d'écho est configuré pour accepter l'en-tête X-Custom
, mais pas l'en-tête Y-Custom
(vous pouvez ouvrir echo-servers/cors-server.js
et rechercher Access-Control-Allow-Headers
pour le vérifier). Chaque fois qu'un en-tête personnalisé est défini, le navigateur effectue une vérification prévol. Cela signifie que le navigateur envoie d'abord une requête OPTIONS au serveur pour déterminer les méthodes et les en-têtes HTTP autorisés par le serveur. Si le serveur est configuré pour accepter la méthode et les en-têtes de la requête d'origine, celle-ci est envoyée. Sinon, une erreur est générée.
Pour en savoir plus
Code de solution
Pour obtenir une copie du code fonctionnel, accédez au dossier solution.
Vous savez maintenant comment utiliser l'API Fetch.
Ressources
Pour voir tous les ateliers de programmation du cours de formation sur les PWA, consultez l'atelier de programmation de bienvenue.