11528 sujets

JavaScript, DOM et API Web HTML5

Pages :
Bonjour à tous
Dans le cadre du passage de l'un de mes sites en "JavaScript à la mode", j'essaie de comprendre comment utiliser fetch.
La documentation dit
a écrit :
Ce genre de fonctionnalité était auparavant réalisé avec XMLHttpRequest. Fetch fournit une meilleure alternative


Avant de savoir si c'est "meilleur" j'aimerais d'abord comprendre comment on s'en sert, et c'est loin d'être le cas.

Depuis des années, chaque fois que je veux obtenir des données depuis mon site, j'utilise une fonction

function GetRemoteData(url, data, whenDone, args){
	const xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function() {
		if(this.readyState == 4 && this.status == 200) {
			whenDone(this.responseText, args);
		}
	}
	xhr.open('post', url);
	xhr.send(JSON.stringify(data));
}

C'est effectivement un peu complexe comme code, mais je n'y touche jamais (la dernière modif a été de remplacer "var" par "const").
Je me contente d'appeler la fonction en écrivant

GetRemoteData(url, data, whenDone[, args]);

où "url" est la page (.php général) à appeler, "data" les données sous la forme d'un objet qui sera transmis en JSON à la page en question, "whenDone" la fonction appelée lorsque la requête est terminée avec le résultat comme paramètre et éventuellement des paramètres supplémentaires dans "args". En fait dans 95% des cas la fonction à appeler est une méthode d'objet et je n'ai pas besoin d'arguments car la méthode retrouve les infos dans les propriétés de l'objet.

J'ai essayé d'écrire cette fonction en utilisant fetch, je n'arrive à rien et plus je lis la documentation moins je comprends.
Peut être devrais-je arrêter à mon âge d'essayer de comprendre les nouvelles techno ?
Modifié par PapyJP (08 Nov 2022 - 17:49)
Modérateur
Salut,

1. Utiliser les promesses.
2. fetch
3. Expression async function
4. Expression async function* (c'est lorsque tu souhaites créer une fonction génératrice/un générateur)
5. await

Pour faire court, fetch() va te retourner un objet Promise (une réponse). Toutefois, il faut savoir que certaines méthodes comme .json() vont te retourner une promesse.

fetch(instance_request, instance_header).then(response => response.json()).then(data => {
  //...
})

Modifié par niuxe (08 Nov 2022 - 18:27)
Merci de ta réponse

J’ai déjà lu tout ça et plus je lis moins je comprends comment utiliser ces choses prétendument meilleures que mes vieilleries.
J’ai l’impression qu’il faut mettre en œuvre des tas de mécanismes pour simplement faire un appel à une fonction distantes, sujet que j’avais travaillé au niveau recherche au début des années 1990. Notre problème principal était de coder les données et les résultats en xml si le me souviens bien, et de définir une sorte de super DTD pour exprimer les données.

La première chose que je ne comprends pas c’est comment passer des données à la page php. Actuellement je transforme mes données par JSON.stringify et le php les récupère par php.://input. Pas vu comment on fait ça avec fetch

La deuxième chose que je ne comprends pas c’est ce qui se passe si le format des données reçues n’est pas en json correct (par erreur de la page php) ce qui est un cas courant en mode debugging

Un peu de lumière sur ces choses qui me semblent obscures me ferait le plus grand bien.
Après pas mal d'essais et erreurs j'ai fini par comprendre comment on passe les données à la page php, par contre je ne comprends pas comment utiliser le résultat de la promesse.


async function GetRemoteData(url, data, whenDone, args){
	const response = await fetch(url, {
		method: 'post',
		body: JSON.stringify(data)});
	/* pas compris comment récupérer le résultat  si OK et faire alert(erreur) sinon */
}

En analysant avec les outils de fonctionnements, je vois que l'objet "reponse" a certaines propriétés, dont "ok" qui devrait me permettre de savoir comment la requête s'est déroulée, par contre je ne vois pas comment récupérer le résultat.

Je continue mes essais...

async function GetRemoteData(url, data, whenDone, args){
	const response = await fetch(url, {
		method: 'post',
		body: JSON.stringify(data)});
	result = await response.text();
	whenDone(result, args);
}
Modérateur
Bonjour,

Il me semble qu'on peut se contenter de ça :
async function GetRemoteData(url, data, whenDone, args)
{
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
	.then(r => r.text())
	.then(r => whenDone(r, args));
}

On peut aussi bien sûr rajouter du traitement d'erreurs.

Amicalement,
Modifié par parsimonhi (09 Nov 2022 - 23:25)
Merci de proposer cette amélioration.
Je suis très perturbé par cette syntaxe qui ne m'est pas familière et ne correspond à rien de que j'ai pratiqué depuis 65 ans et qui est pour beaucoup dans le fait que je n'ai pratiquement rien compris dans les documents que j'ai lus.
Tout d'abord écrire fetch() .then() .then(); me semble farfelu. Je ne comprends pas quand les ".then()" s'exécutent. Est-ce que cela correspond aux "await" de ce que j'ai écrits, c'est à dire qu'un ".then" s'exécute à la complétion de l'opération qui le précède ?
Ensuite les "r" semblent venir de nulle part. Est-ce que "r" veut dire "résultat de l'action précédente?
Par ailleurs je n'ai pas non plus compris comment on faisait pour traiter les refus, soit par exemple une erreur 404 sur l'exécution du fetch, ou bien pas de texte dans la réponse, ou encore -- si on utilise .json() au lieu de .text() -- si le texte reçu n'est pas du json.

Un peu d'explication pourrait m'aider dans la compréhension de ce qui se présente comme étant du JavaScript mais a l'apparence d'un nouveau langage.
Modérateur
PapyJP a écrit :

Je suis très perturbé par cette syntaxe qui ne m'est pas familière et ne correspond à rien de que j'ai pratiqué depuis 65 ans et qui est pour beaucoup dans le fait que je n'ai pratiquement rien compris dans les documents que j'ai lus.


Tant que tu n'as pas lu et assimilé les promesses et le traitement asynchrone, tu ne pourras pas comprendre les concepts.

PapyJP a écrit :

Tout d'abord écrire fetch() .then() .then(); me semble farfelu. Je ne comprends pas quand les ".then()" s'exécutent.


niuxe a écrit :

Pour faire court, fetch() va te retourner un objet Promise (une réponse). Toutefois, il faut savoir que certaines méthodes comme .json() vont te retourner une promesse.


Tu n'as pas bien lu ma réponse et les liens que je t'ai partagés à ton sujet. J'ai été précis et concis. J'ai préféré partager les liens. Ainsi, tu as une documentation optimale pour comprendre les concepts liés à fetch.
Je pense qu'une relecture s'impose :
- les promesses

PapyJP a écrit :

Est-ce que cela correspond aux "await" de ce que j'ai écrits, c'est à dire qu'un ".then" s'exécute à la complétion de l'opération qui le précède ?
Ensuite les "r" semblent venir de nulle part. Est-ce que "r" veut dire "résultat de l'action précédente?

Je pense qu'une relecture s'impose aussi :
- closures



PapyJP a écrit :

Par ailleurs je n'ai pas non plus compris comment on faisait pour traiter les refus, soit par exemple une erreur 404 sur l'exécution du fetch, ou bien pas de texte dans la réponse, ou encore -- si on utilise .json() au lieu de .text() -- si le texte reçu n'est pas du json.

Je pense qu'une relecture s'impose :
- les promesses
- Error

PapyJP a écrit :

Un peu d'explication pourrait m'aider dans la compréhension de ce qui se présente comme étant du JavaScript mais a l'apparence d'un nouveau langage.


La syntaxe a légèrement changé. Mais les fondements du langage restent les mêmes.


@parsimonhi: async/await ==> tu as plus de souplesse et un code plus concis (ça t'évite les grappes de codes imbriqués dans des promesses). async/await = ES2017 (de mémoire)
Modifié par niuxe (10 Nov 2022 - 18:43)
Il me semble que j’ai compris les mécanismes qui sont relativement simples. Quand on a passé une bonne partie de sa carrière à gérer du temps réel et des télécoms, il n’y a rien de surprenant là dedans. C’est la façon de les écrire qui me perturbe.
Je vais reprendre la lecture des documents et essayer de mettre ça en pratique.
Modérateur
PapyJP a écrit :
Il me semble que j’ai compris les mécanismes qui sont relativement simples.


C'est le principal. Cependant, derrière ce mécanisme simple, il y a beaucoup de concepts/théories à aborder. Tant que ça n'est pas acquis, la compréhension de l'api fetch ne peut être appréhender correctement.

Pourquoi fetch ?
L'objet XMLHttpRequest vient d'un autre temps. fetch va rendre ton code nettement plus homogène (pas forcément plus concis) et plus lisible.
Modifié par niuxe (10 Nov 2022 - 18:04)
Modérateur
PapyJP a écrit :
Tout d'abord écrire fetch() .then() .then();; me semble farfelu.


Je viens de comprendre pourquoi tu n'assimiles pas bien la suite des méthodes then(). En POO, lorsque tu veux enchainer les appels aux méthodes d'un objet, tu dois soit, lui retourner lui-même (this), soit retourner une instance d'un objet.


class A{
        constructor(){
            console.log('A');
        }

        run(){
            console.log('run A');
            return this;
        }

        get(){
            console.log('get A');
            return this;
        }
    }

    class B{
        constructor(){
            console.log('B');
            this.a = new A()
        }

        run(){
            console.log('run B');
            return this.a
        }
    }

    let aa = new A();
    aa.get().run().get().run();

    let bb = new B()
    bb.run().get()



niuxe a écrit :
Pour faire court, fetch() va te retourner un objet Promise (une réponse). Toutefois, il faut savoir que certaines méthodes comme .json() vont te retourner une promesse.

Modifié par niuxe (10 Nov 2022 - 18:28)
Oui, j’ai très bien compris le mécanisme.
Je pratique la POO depuis que ça a débarqué en France dans les années 1985, donc ce n’est pas là le problème, encore une fois c’est la façon de l’écrire qui me gêne mais je vais finir par m’y faire. Mettre une chaîne d’appels comme tu le fais dans ton exemple est peut-être très élégant mais ce n’est pas facile à lire.

Par expérience professionnelle ce qui est important dans un programme c’est qu’il soit lisible et maintenable par un tiers.
En 1975 j’ai récupéré un programme de plusieurs milliers de lignes qu’il a fallu deux ans pour le rendre maintenable car il fallait avant tout traiter les bugs signalés par les clients et sortir des versions de maintenance intermédiaires avant de tout réécrire
Un programme bien écrit se lit comme un roman.
Il est découpé en phrases courtes et lisibles qui ne font pas plus d’une chose à la fois.
Par exemple je me refuse à écrire "e" au lieu de "event" ou "error", "r" plutôt que "response" etc. parce que c’est illisible pour un nouveau venu, et quand je reprends un programme que j’ai écrit il y a 2 ans je suis comme un nouveau venu.

Il faut que j’assimile ça et le mette à ma sauce.
Modifié par PapyJP (10 Nov 2022 - 23:40)
Modérateur
Bonjour,

PapyJP a écrit :
Tout d'abord écrire fetch() .then() .then(); me semble farfelu. Je ne comprends pas quand les ".then()" s'exécutent. Est-ce que cela correspond aux "await" de ce que j'ai écrits, c'est à dire qu'un ".then" s'exécute à la complétion de l'opération qui le précède ?
Oui, on peut voir ça comme ça.
PapyJP a écrit :
Ensuite les "r" semblent venir de nulle part. Est-ce que "r" veut dire "résultat de l'action précédente?
Oui, c'est ça. J'ai mis "r", mais tu peux mettre n'importe quel nom.
PapyJP a écrit :
Par ailleurs je n'ai pas non plus compris comment on faisait pour traiter les refus, soit par exemple une erreur 404 sur l'exécution du fetch, ou bien pas de texte dans la réponse, ou encore -- si on utilise .json() au lieu de .text() -- si le texte reçu n'est pas du json.
Pour le traitement des erreurs, ça dépend pas mal du contexte et des erreurs qu'on souhaite traiter. C'est du cas par cas. Un peu plus haut, tu as évoqué l'idée d'utiliser response.ok. Voilà ce que ça pourrait donner dans le cas de GetRemoteData() :
async function GetRemoteData(url, data, whenDone, args)
{
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
	// Le "then" ci-dessous s'exécute une fois que "fetch" a retourné une réponse.
	.then(response => {if(response.ok) return response.text(); else return false;})
	// Le "then" ci-dessous s'exécute une fois que le "then" précédant a retourné un résultat.
	// Si ce résultat vaut false, on peut convenir qu'il s'agit d'une erreur.
	// Il suffira alors dans la fonction whenDone() de tester si "result" vaut false,
	// et si c'est le cas d'y faire un traitement approprié pour gérer l'erreur.
	// L'exemple est ici rudimentaire.
	// On peut imaginer des traitements bien plus complexes évidemment.
	.then(result => whenDone(result, args));
}

On aurait pu aussi utiliser "throw" ... "catch" (voir un exemple à https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).

NB: à l'intérieur des .then(), on utilise des fonctions dites fléchées car la méthode then() d'un objet Promise prend pour paramètres des fonctions callback. La fonction fetch() retourne un objet Promise. Le "." qu'il y a entre fetch() et then() n'est rien de plus que l'habituelle manière d'exécuter une méthode d'un objet. Et la méthode then() retourne elle aussi un objet Promise, ce qui permet donc de chainer les then() les uns après les autres. On remarquera au passage que :
.then(a => f(a))
est équivalent à
.then(a => {return f(a);})
qui est aussi équivalent à
.then(function(a){return f(a);})

@niuxe: en ce qui concerne async/await, oui, quand ça se complexifie un peu, ça peut permettre de mieux s'y retrouver. Mais dans l'exemple de la fonction GetRemoteData() de PapyJP, le chainage à l'aide des .then() me plaisait bien.

Amicalement,
J’ai fini par comprendre ce qui m’empêchait d’aller plus loin, c’est de fait que les .then ne soient pas directement accolés à l’objet.

Quand j’ai appris ce langage il y a… très longtemps une fin de ligne signalait la fin d’une instruction (avec ou sans ";")
On n’utilisait".property" ou ".method()" que dans le portée d’une instruction "with".

with(myObject) {
   .a = ….;
   var b = .myMethod(…);
   ……
}

et il n’était pas question de ne pas accoler l’utilisation d’une propriété ou d’une méthode au nom de l’objet.

Apparemment on considère aujourd’hui que "a.b.c" peut s’écrire "a
.b
.c"
Ce n’est peut-être pas une nouveauté mais pour moi c’est le contraire de ce que j’ai pratiqué depuis 30 ans.

Quand je disais que c’était une histoire de syntaxe…
Modifié par PapyJP (11 Nov 2022 - 09:08)
Suite à cette discussion à laquelle je vous remercie d'avoir participé, j'ai d'abord vérifié comment se comporte le moteur JavaScript par rapport au "."
Ma conclusion est que "." se comporte comme un opérateur, on peut mettre des espaces ou des newlines de part est d'autre.
De même qu'on écrit

let a = b+c;
let a = b +c;
let a = b + c;
let a = b
+
c;

on peut écrire

let a = myObject.b;
let a = myObject .b;
let a = myObject . b;
let a = myObject
 . 
b;
(même si le dernier exemple n'est pas à recommander, car autant un "+" tout seul sur un ligne se lit aisément il n'en est pas de même pour un "."

C'est peut-être évident pour vous, mais c'est une découverte pour moi.

J'ai écrit (et testé) la fonction sous la forme suivante

async function GetRemoteData(url, data, whenDone, args){
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
		.then(response => response.text())
		.then(text => whenDone(text, args));
}

l'indentation et l'utilisation de "response" et "text" correspondent à mes "bonnes pratiques" d'écriture de code : si je dois relire ce code dans 2 ans je devrais le comprendre sans difficulté.

Reste à écrire les traitements d'erreur.
J'ai essayé sans succès d'écrire

async function GetRemoteData(url, data, whenDone, args){
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
		.then(response => {
			if(responses.ok) return response.text();
			else alert(response.error());
		})
		.then(text => whenDone(text, args));
}

Qu'est-ce qui cloche dans ce code ?
Modérateur
Bonjour,

1) Il y a un "s" en trop dans l'expression "responses.ok".

2) La méthode error() n'est pas "disponible ici de toute façon ( voir https://developer.mozilla.org/en-US/docs/Web/API/Response/error ). Mais tu peux par exemple utiliser response.status ou response.statusText, ou encore fabriquer tes propres messages d'erreur selon le status de response. Par exemple :
async function GetRemoteData(url, data, whenDone, args){
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
		.then(response => {
			if(response.ok) return response.text();
			else {
				alert("Error: "+response.statusText);
				return false;
			}
		})
		.then(text => whenDone(text, args));
}


3) J'ai pour habitude de faire en sorte que la ligne else alert(...) retourne comme la ligne if(...) qui est juste au dessus quelque chose en plus du alert(...) (genre un simple return false ou ce que tu veux d'autre), afin que le .then(text => whenDone(text, args)); puisse savoir sans ambiguïté qu'il y a eu une erreur à l'étape précédente en testant si text vaut false ou ce que tu as voulu d'autre. Ou alors tu peux utiliser le mécanisme throw ... catch. Comme je sens qu'on va y arriver dans pas longtemps de toute façon, voici un exemple :
async function GetRemoteData(url, data, whenDone, args){
	fetch(url, {method: 'POST', body: JSON.stringify(data)})
		.then(response => {
			if(!response.ok) throw response.statusText;
			return response.text();
		})
		.then(text => whenDone(text, args))
		.catch(error => alert("Error: "+error));
}


Amicalement,
Modifié par parsimonhi (11 Nov 2022 - 15:53)
Merci. Je commence à y voir plus clair.

Sauf le .catch()
Écrit comme ça ça semble être une méthode du dernier objet retourné par le .then qui le précède lequel ne retourne rien me semble-t-il, ou alors c’est le return de la fonction whenDone mais là aussi ce n’est pas sensé retourner quelque chose.
Encore une subtilité d’écriture qui m’échappe.
Modifié par PapyJP (11 Nov 2022 - 15:04)
Modérateur
Bonjour,

Pour le throw ... catch, le processus est le suivant :

Si (!response.ok), le code entre le throw et le .catch() est ignoré et on exécute donc le code du .catch() sans exécuter le .then(text => whenDone(text, args)). En gros, le throw est un "go to" le premier .catch() se trouvant dans le code après le throw.

EDIT: et évidemment "error" dans la ligne .catch(error => alert("Error: "+error)); vaut la valeur de ce qu'il y a après le throw, c'est à dire dans notre cas response.statusText.

Si (response.ok), tout le code est exécuté sauf le .catch() qui n'exécutera pas la fonction callback qu'on lui donne en paramètre.

Amicalement,
Modifié par parsimonhi (11 Nov 2022 - 16:31)
connecté
Merci à vous, c'était super intéressant (j'ai toujours eu du mal avec l'asynchrone). Votre discution donne une autre perspective que celle des tutoriels, une approche complémentaire en quelque sorte. Made in cocorico, ce qui n'est pas dommage, ça change de stackoverflow.