Faire le quiz close

Encapsuler des requêtes GET ou POST dans des fonctions avec callback.

#1 Un peu de conception préliminaire open

Je vais faire un peu de conception préliminaire avant de me lancer tête baissée dans le codage de mes fonctions 😎. Pour ça je vais préciser mon besoin et ensuite, je vais voir comment y répondre.

  1. Je veux deux fonctions qui vont chacune faire une requête Ajax. La première va faire la requête en utilisant la méthode GET, je vais l'appeler getData. La deuxième va faire la requête en utilisant la méthode POST, je vais l'appeler postData.
  2. Pour chacune de ces fonctions, je vais utiliser XMLHttpRequest pour faire la requête. J'ai déjà fait un tuto sur XMLHttpRequest. Je m'en servirai.
  3. Dans les deux cas, je veux faire mes requêtes en passant des informations à mon serveur. Je sais que pour faire ça, au niveau de la requête HTTP, j'aurais besoin de passer mes informations dans une query string. J'ai déjà parlé de ça dans le tuto sur les requêtes HTTP.

    Cette query string, il faudra la fabriquer. Je la fabriquerai dans chaque fonction à partir d'un objet que je passerai en premier argument de mes fonctions. Cet objet, je vais l'appeler params. Il faut se rappeler qu'il faut url encoder la query string pour le POST.

    Comment passer des informations à la requête.
    Besoin de transformer l'objet params en query string
  4. Maintenant, il faut voir un truc important 🙂. J'ai parlé de callback dans le titre du tuto. Pourquoi ? Eh bien, parce que dans ces fonctions, je vais faire une requête, mais je dois aussi prévoir d'installer un gestionnaire d'événement qui traitera la réponse. Une réponse qui ne sera pas immédiate puisque l'on est asynchrone. Il faut surtout pas oublier ça ! En plus il y aura une réponse en cas de succès et une autre réponse en cas d'échec.

    Donc deux traitements différents. En conséquence, en argument de mes fonctions, il faudra que je passe deux fonctions qui contiendront les traitements à faire en cas de succès et en cas d'échec. Je vais appeler ces arguments succes et echec. Vous avez bien compris que les fonctions getData et postData s'exécuteront de manière non bloquante, c'est-à-dire sans se bloquer pour attendre la réponse de la requête.

    Ce ne sont pas les fonctions getData et postData qui traiteront la réponse. Ce sont les gestionnaires d'événements mis en place par ces fonctions qui enclencheront les callbacks le moment venu 😌.

    Pourquoi faut-t-il passer des callbacks en argument.
    Pourquoi doit-on passer des callbacks
  5. Je vais aller un peu plus loin dans la conception de la transformation de l'objet params en query string. Ce que je peux faire c'est prévoir une boucle for in de manière à récupérer le nom et la valeur de chaque propriété de l'objet.

    Ensuite, je peux stocker ces informations dans un tableau par couple nom=valeur. Ce tableau, je vais l'appeler query. S'il faut url encoder, je pourrais le faire là.

    Ensuite avec un appel à la méthode query.join('&') avec en argument l'esperluette (et commercial) et j'aurai ma query string.

    Comment passer des informations à la requête.
    Besoin de transformer l'objet params en query string

#2 Fonction avec requête GET open

  1. Maintenant que j'ai fait ce petit travail de conception, je peux attaquer serein le codage de ma fonction.
  2. Je récupère le code d'une requête HTTP sur le tuto de XMLHttpRequest .
  3. Je vais quand même modifier le gestionnaire. Je vais attendre l'événement onload au lieu de l'événement readystatechange. L'événement onload est déclenché lorsque la requête est terminée. Ca évite la cuisine sur la propriété readyState.

    Par contre, il faut toujours vérifier le status. S'il vaut 200, alors j'appelle succes() et sinon j'appelle echec(). Quels arguments je vais passer aux callbacks ? A succes() je passe la réponse que je prends dans le champ responseText. A echec() je vais m'amuser à passer deux arguments. La valeur numérique du code de retour en premier. Ca, je le prends dans status puis la chaîne qui correspond au code en second et ça je le prends dans statusText.

    Pourquoi je fais ça ? Eh bien, pour vous montrer que vous devez être cohérent. Si vous mettez deux arguments ici, au moment de l'appel du callback, eh bien évidemment au moment de la déclaration du callback, il faudra prévoir deux paramètres. JavaScript ne vous interdira pas d'en mettre qu'un seul, il n'est pas contrariant JavaScript, mais à ce moment-là, vous perdrez le deuxième.

  4. Maintenant, je vais travailler la transformation params en query string.
    1. Je vais déclarer mon tableau query.
    2. Je vais mettre en place ma boucle for in.
    3. Je vais faire un push en concaténant le nom de la propriété et sa valeur.
    4. Je vais faire le join au niveau de l'uri.
    5. Je vais modifier l'uri car celle-là, c'est celle de l'ancien tuto.
  5. Maintenant, je vais passer à l'appel de la fonction :

    1. Je vais déclarer un objet params pour le test.
    2. Je code l'appel à getData.
    3. Je mets params en premier argument.
    4. Je déclare les callbacks. Pour succes, je fais un simple console.log. Pour echec, je prends bien soin de déclarer deux paramètres status et statusText et je concatène les deux dans un console.log.

Ci-dessous, le code PHP du fichier succes.php que je vais utiliser pour traiter la requête. Je récupère les paramètres avec la superglobale $_REQUEST. De cette manière je vais pouvoir utiliser ce traitement pour la requête GET et pour la requête POST. Ce n'est pas "une bonne pratique" de procéder de cette manière mais c'est juste pour un test. Dans le script ci-dessous, je récupère les paramètres de la requête et je les renvoie. Cet aller / retour me permet de valider la communication.

Tester le code
Résultat du succès d'une requête
Résultat d'une execution réussi de getData

Maintenant, je vais simuler un échec en renvoyant une en-tête Bad Request. Le code php du fichier echec.php est ci-dessous. Côté JavaScript cet échec va être pris en charge par le callback echec.

Tester le code
Résultat de l'échec d'une requête
Résultat d'une exécution en échec de getData

Ci-dessus, la trace en rouge dans le navigateur laisse penser qu'une exception JavaScript a été levée. Ce n'est pas le cas.

En fait, côté client, au niveau JavaScript, toutes les instructions se sont exécuter correctement.

C'est le navigateur qui note une erreur réseau. Le "problème" c'est que graphiquement, il le fait comme pour une exception JavaScript. Ca peut être trompeur. Si vous voulez la supprimer (sur Chrome) vous pouvez aller dans Console settings (roue crantée) puis vous cochez Hide Network (Voir ci-dessous).

Cacher les erreurs du réseau

#3 Fonction avec requête POST open

Le code d'une requête avec POST est très similaire à celui d'une requête avec GET. La différence se situe au niveau du traitement des paramètres, qui se trouvent, je le rappelle, dans l'objet params.

Pour une requête POST il va falloir :

  1. Url encoder ces couples paramètre / valeur. On fait ça avec encodeURIComponent.
  2. Envoyer une en-tête qui spécifie le format url encoded de ces paramètres. On fait ça avec xhr.setRequestHeader. A mettre entre le xhr.open et le xhr.send.
  3. Mettre ces paramètres dans le corps de la requête HTTP. Pour ça on passe la query string en argument du xhr.send.

Le code php utilisé pour la requête POST est le même que celui utilisé pour la requête GET.

Tester le code
Résultat du succès de la requête POST
Résultat d'une exécution réussi de postData

#4 Envoi d'une salve de requêtes asynchrones puis d'une salve de requêtes synchrones open

Je vais envoyer simultanément plusieurs requêtes. Cinq en fait. Je vais d'abord le faire de manière asynchrone, puis je vais modifier un argument du xhr.open pour le faire de manière synchrone. On va observer les comportements. Je vais me servir de la fonction getData que je vais mettre dans une boucle for. Je vais passer en premier argument de getData un objet {num : i} qui va me servir à numéroter les requêtes.

Du côté php, je mets en place un script server-timer.php qui va générer un nombre aléatoire compris en 1 et 5. Je vais me servir de ce nombre pour faire un sleep de chaque processus qui traitera une requête. Ce nombre représentera la durée en secondes. Les processus seront endormis pendant cette durée. Je dis les processus, car sur un serveur qui tourne sous un OS Unix chaque requête crée un processus. (Ca ne serait pas la même chose sur un serveur Node.js / Express).

Pour bien comprendre ce qui se passe du côté serveur, il faut un peu s'éloigner du sujet. Chaque demande de requête est faite sur le port 80 (443 si https). Le serveur http (daemon httpd) fait un fork à chaque requête. Ca veut dire que chaque requête crée un processus indépendant qui va s'endormir pendant $rt secondes puis se réveiller pour envoyer sa réponse. Le client (navigateur) va recevoir les réponses de manière aléatoire. C'est là que je voulais en venir.

Explication de ce qui se passe du côté serveur.

Ci-dessous, les requêtes asynchrones sont toutes envoyées en même temps. On voit que le processus qui traite la requête numéro 3 se réveille au bout de 1 seconde. Il envoie la réponse. Elle est traitée par le navigateur. Ensuite, c'est au tour de la requête 0, dont le processus se réveille au bout de 2 secondes...et ainsi de suite.

Tester le code
Résultat avec plusieurs requêtes asynchrones simultanées

Ci-dessous, les requêtes synchrones sont envoyées les unes après les autres. La requête 0 est envoyée. Elle répond au bout de 2 secondes. L'appel étant bloquant, la requête 1 n'est envoyée que lorsque la requête 0 a répondu. Elle répond au bout de 2 secondes. Et ainsi de suite. On voit que les réponses se succèdent dans l'ordre de la numérotation des requêtes.

Attention. On a un message qui nous dit que ce mode de fonctionnement est déprécié car il a un mauvais effet sur l'expérience de l'utilisateur final. Effectivement ces requêtes étant bloquantes les entrées / sorties clavier et souris ne sont plus gérées par le navigateur pendant la durée de traitement des requêtes. Autrement dit, l'utilisateur est bloqué. On a déjà vu ça dans le tutoriel Qu'est-ce qu'une requête asynchrone ?

Tester le code
Résultat avec plusieurs requêtes synchrones simultanées