On voit donc que le code JS s’exécute très vite, en quelques millisecondes.
L'utilisateur ne s’aperçoit pas de ce blocage très court.
A la fin de notre code JS (fin du <script>), on rend la main au navigateur.
Que fait le navigateur ensuite ?
Le navigateur passe la plupart de son temps dans la « boucle d'événements »... à ne rien faire.
De temps en temps (au plus toutes les 16 millisecondes), il se rend compte que quelque-chose à changé (par exemple, le chat animé a bougé) et qu'il est temps de mettre à jour l'affichage.
Alors, il met à jour l'affichage.
Lorsqu'on appuie sur le bouton, le navigateur ajoute une tache JS à exécuter dans la liste (à gauche de l'animation ci-dessous).
Lorsque ce code JS a fini de s'exécuter il rend la main au navigateur, qui retourne dans la boucle d'événements.
Plus tard, il rafraîchit l'affichage si nécessaire (l'image du chat bouge).
Lorsque le JS bloque, le navigateur reste coincé et ne retourne pas à la boucle d'événements.
Remettons la boucle infinie:
while(true);
Essayons de comprendre le déplacement de l'image de la souris.
Explications:
console.log('début'); setTimeout(()=>{ console.log('bonjour'); }, 5000); console.log('fin')
Le code suivant
setTimeout(xyz, 1234)
permet de dire au navigateur « Tiens, prends cette fonction xyz et enregistre-là pour l'exécuter plus tard » (dans 1234 ms).
On a fourni au navigateur une action qu'il doit faire plus tard.
Le code suivant
document.addEventListener('click',xyz);
permet de dire au navigateur « Tiens, prends cette fonction xyz et exécute-là plus tard » (quand l'utilisateur clique)
On a fourni au navigateur une action qu'il doit faire plus tard.
En JS, on cherche souvent à faire exécuter des actions plus tard. Ces actions sont des fonctions.
C'est pour ça qu'on a autant besoin de manipuler des fonctions. Les fonctions anonymes sont une manière pratique (mais pas indispensable) de le faire.
Une fonction qu'on fournit pour être appelée plus tard s’appelle un « callback »
Pour enchaîner dans le temps des opérations, on a dû emboîter des fonctions. Ce n'est pas très lisible.
Ici, on n'en a emboîté que 3, mais en pratique, ça peut être bien pire.
Cette situation arrive souvent en JS quand on veut enchaîner des opérations qui sont décalées dans le temps.
Cette situation a un nom: « Callback Hell ».
Pour résoudre ce problème et faciliter la vie des développeurs, le JS propose deux outils, liés:
const p=range_ta_chambre('Tom');
console.log(p);
(Essayez dans votre <script> ou dans la console avant de répondre)
(utilisez une fonction fléchée, sans paramètre, et avec des accolades {...} )
Comment faire pour réagir si la promesse n'est pas tenue (elle est rompue) ?
Comme la méthode then(...), les promesses ont une méthode catch(...), qui prend une fonction en paramètre:
const p=range_ta_chambre('Tom');Essayez dans votre navigateur.
p.catch(()=>{console.log('Tu es vilain!');});
La méthode p.then(...) renvoie une nouvelle promesse.
On pourrait la mettre dans une nouvelle variable « p2 », comme ceci:
let p=range_ta_chambre('Tom');
let p2=p.then(()=>{console.log('Trés bien!');});
Mais, le plus souvent, on l'utilisera directement, pour enchaîner avec catch:
p.then(()=>{console.log('Trés bien!');}).catch(()=>{console.log('Tu es vilain!');});
La même chose en 2 lignes:
p.then( ()=>{console.log('Trés bien!');})
.catch(()=>{console.log('Tu es vilain!');});
Très souvent, on ne met pas non plus la première promesse (p) dans une variable. Ce qui donne:
range_ta_chambre('Tom')Essayez le code précédent. Vous ne devriez plus avoir de message d’erreur.
.then( ()=>{console.log('Trés bien!');})
.catch(()=>{console.log('Tu es vilain!');});
range_ta_chambre('Tom',1).then(()=>{
range_ta_chambre('Paul',1).then(()=>{
range_ta_chambre('Jean',1);
})
});
On se retrouve dans la situation du chat poursuivant la souris: des fonctions, dans des fonctions, dans des fonctions...
Si la fonction (ici anonyme) fournie à .then() retourne une promesse, alors on peut enchaîner les appels à .then() :
range_ta_chambre('Tom',1)
.then(()=>{return range_ta_chambre('Paul',1);})
.then(()=>{return range_ta_chambre('Jean',1);})
C'est plus propre.
Les promesses aident à simplifier le code, mais restent lourdes à utiliser.
Ceci étant, elles permettent (parfois) l'utilisation de « await »... qui simplifie énormément le code 😀
« await » permet de faire comme si on attendait, sans réellement bloquer le JS, donc en rendant la main au navigateur.
console.log('bonjour');
await attendre(5000);
console.log("ceci s'affiche vraiment 5s après !!! :-)");
Explication:
await est vraiment très pratique... et a un coté un peu « magique »
await fait des choses « bizarres ». Il arrête l’exécution en plein milieu d'une fonction, pour la reprendre, plus tard, au même endroit.
Pour que ça puisse marcher, la fonction qui contient await doit être un peu particulière. Elle doit être déclaré comme « async »:
async function exemple() {
console.log('bonjour');
await attendre(5000);
console.log("ceci s'affiche vraiment 5s après !!! :-)");
}
<script>
await attendre(5); // erreur !
</script>
<script type="module">ATTENTION: 2023:
await attendre(5); // ok
</script>
<script>
(async function(){
// votre code
await exemple();
})();
</script>
Voyons le déroulement de « await »:
Toutes le requêtes qu'on a vu, sont faites au chargement de la page.
Le navigateur reçoit le HTML initial et le traite. Quand il y trouve des ressources externes (images, JS, CSS, ...); il les télécharge.
Dans le premier exemple, quand le navigateur voit ceci:
<img id="chat" src="chat.png" alt="chat"/>
il déclenche le téléchargement de https://moodle.iutv.univ-paris13.fr/img/bjs2/chat.png
Les interactions avec le serveur sont aussi possibles après le chargement de la page:
Voyons quelques exemples.
Quand un utilisateur commence à taper une recherche dans Google, Google lui propose une liste de suggestions.
Il est impossible d'inclure dans la page d'origine toutes les suggestions possibles. Le navigateur va donc demander au serveur la liste de suggestions au fur et à mesure que l'utilisateur écrit. Ceci se fait sans recharger la page.
Dans ce qu'on a vu jusqu'à maintenant, les interactions avec le serveur se faisaient au moment du chargement de la page.
Ici, la liste des suggestions est cherchée à partir du serveur, à partir du JS et après la fin du chargement de la page.
Dans cet exemple, l'utilisateur appuie sur un bouton « J'aime » dans les commentaires d'une vidéo sur Youtube. Son action est enregistrée sur le serveur, mais la page ne se recharge pas.
Le fonctionnement habituel en HTML du bouton consiste à envoyer une requête POST et afficher une nouvelle page. Le rechargement de la page est désagréable pour l'utilisateur. C'est lent, la vidéo s'interrompt, il perd sa position dans la page...
Ici, la requête sur le serveur se fait sans rechargement de la page.
AJAX veut dire « Asynchronous JavaScript and XML »
En pratique, les requêtes Ajax peuvent se faire de différentes manières:
On peut envoyer des informations au serveur, tout simplement en les mettant dans l'URL d'une requête.
On commence les paramètre GET avec un « ? ». Ensuite on met le nom (abc) et sa valeur (def) associée:
http://exemple.org/ma-page.php?abc=def
Si on veut ajouter plusieurs valeurs, on utilise un « & »:
http://exemple.org/ma-page.php?abc=def&rst=uuuuu
Écrivez l'URL suivante en ajoutant un paramètre GET appelé « nom » et ayant comme valeur « Tom »
https://moodle.iutv.univ-paris13.fr/img/bjs2/sujet-3-fetch-1.php
Les promesses ont une valeur (du moins en JS).
Une promesse représente une opération future, qui peut renvoyer une valeur. C'est la « valeur » de la promesse.
Par exemple, lorsqu'on fait une requête AJAX avec simple_fetch, on veut pouvoir récupérer la réponse du serveur.
simple_fetch() renvoie une promesse. La réponse du serveur sera la « valeur » de la promesse. Cette valeur n'est pas disponible au départ. Elle n'est disponible que lorsque la promesse est tenue. On ne peut récupérer la valeur qu'après que la promesse soit tenue, c'est à dire dans la fonction donnée à .then() ou bien après « await ».
Les deux manières de récupérer:
1. En paramètre de la fonction:
simple_fetch('http://exemple.org/abc.php').then( (valeur)=>{console.log(valeur);});
2. Lorsqu'on utilise « await »
let valeur= await simple_fetch('http://exemple.org/abc.php');
console.log(valeur);
Dans le développement web on parle aussi de « frontend » pour ce qui se passe dans le navigateur et de « backend » pour ce qui se passe sur le serveur.
chat.php est déjà écrit, vous n'aurez pas à le modifier.
C'est une situation classique dans laquelle on se retrouve quand on développe en « frontend »:
On développe du code JS « frontend » sur le navigateur.
On accède à un service « backend » existant déjà sur un serveur. Ce « backend » fournit une API.
L'API consiste ici, des URL suivantes:
http://localhost/~__user_nb__/chat.php?action=messages
http://localhost/~__user_nb__/chat.php?action=nom
http://localhost/~__user_nb__/chat.php?action=changer-nom
http://localhost/~__user_nb__/chat.php?action=envoyer
http://localhost/~__user_nb__/chat.php?action=reinitialiser
http://localhost/~__user_nb__/chat.php?action=test
On a déjà vu « test ». On verra comment utiliser chacune d'entre-elles.
Quand on fait une requête HTTP, on utilise une « méthode ».
La méthode par défaut est « GET ».
Une autre méthode est appelée « POST »
En JS on crée beaucoup d'objets et on se retrouve souvent dans la situation suivante:
let nom='Tom';
let o={ nom: nom };
ou bien
let a='Joe', b=123;
let o={a:a,b:b};
Quand la valeur de la propriété est une variable ayant le même nom que la propriété, le JS permet de raccourcir :-)
{a:a,b:b} se racourcit en {a,b}
Comment se raccourcit {nom:nom} ?
Node.js est un environnement permettant d’exécuter du JS en-dehors du navigateur.
Il peut-être utilisé coté serveur, mais il peut aussi être utilisé dans d'autres contextes, comme la plupart des langages de programmation.
Node.js n’étant pas dans le navigateur, on ne peut pas accéder au DOM.
Par contre, on peut accéder aux fonctionnalités du système, qui ne sont pas disponibles dans le navigateur (pour des raisons de sécurité).
On peut lire et écrire des fichiers, on peut créer des serveurs, on peut créer des processus, on peut accéder à une base de données, etc.
Ouvrez un terminal.
Tapez:$ node
Vous êtes dans une console, similaire à celle du navigateur!
Essayez des instructions, comme « 1+1 » ou « console.log('Bonjour') »
Pour pouvoir utiliser des fonctionnalités fournies par différentes librairies, il faut d'abord aller les chercher.
Vous l'avez vu dans d'autres langages: « import » dans python et Java, « include » ou « require » en PHP. «include » en C.
Node.js propose deux manières de le faire:
import * as fs from 'node:fs/promises';
import * as fs from 'node:fs/promises';Remarquez que readFile retourne une promesse. On utilise « await »
const pass=await fs.readFile('/etc/passwd','utf8');
Dans Node.js on va utiliser une approche « asynchrone », comme dans le navigateur.
C'est à dire, qu'on ne doit pas bloquer l’exécution du JS.
Toutes les opérations qui prennent du temps (lire un fichier, faire une requête à une base de données, attendre une connexion réseau...), doivent se faire en deux temps:
Souvent, on peut utiliser des promesses.
Dans ce cas, on peut utiliser async/await pour éviter d'utiliser un callback:
console.log('avant');
const pass=await fs.readFile('/etc/passwd','utf8');
console.log('après');
...
Sans « await », on aurait du écrire:
console.log('avant');(Remarque: il est possible en Node.js de faire du code qui n'est pas asynchrone, en utilisant des versions bloquantes de fonctions comme readFile. Cette approche est moins utilisée et elle est incompatible avec les logiciels qu'on verra ici: Express, Vue.js ...)
fs.readFile('/etc/passwd','utf8').then(pass=>{
console.log('après');
...
});
Dans l'exemple précédent on a utilisé la librairie 'fs', qui est intégrée à Node.js
En général, on veut aussi utiliser d'autres librairies.
npm est à la fois une commande permettant de gérer des paquets et un répertoire en ligne de paquets qu'on peut installer.
Ce répertoire « npm » fournit plus d'un million de paquets.
Chacun peut y publier ses propres paquets.
Généralement, pour utiliser npm, vous devez mettre votre logiciel dans un répertoire à lui.
La commande npm s’utilise alors dans ce répertoire.
Elle utilise un fichier « package.json » qui décrit votre logiciel et ses dépendances.
Les dépendances sont les paquets ("librairies") dont votre logiciel a besoin pour fonctionner.
npm fournit de nombreuses sous-commandes. En voici deux:
Express et PHP ont des architectures très différentes.
Pour utiliser PHP un logiciel appelé « serveur web » est nécessaire (Apache, NGINX).
Il reçoit les requêtes provenant des navigateurs (http://.../ex1.php, http://.../ex2.php,...)
Il trouve les fichiers PHP correspondants
Il exécute chacune de ces requêtes PHP dans un processus différent
Chaque processus peut durer longtemps (quelques centaines de ms): chaque processus peut s’arrêter en attendant une opération longue (le contenu d'un fichier, le résultat d'une base de données, etc). Le développeur PHP n'a pas besoin de s'occuper de ces attentes. C'est géré automatiquement par le fait qu'il y ait plusieurs processus.
Dans Express, il n'y a pas de serveur séparé. Il n'y a qu'un seul processus. Votre programme (app.js) est le serveur.
Les requêtes sont traitées les unes après les autres. Comme le code JS doit être non-bloquant, chaque requête est traitée très rapidement puis rend la main à la boucle d'événements.
S'il y a une opération longue à gérer, le développeur doit utiliser une des techniques de programmation asynchrone (callback, Promise, await).
C'est un problème de performance.
Lancer des processus (ou même réutiliser des processus existants) c'est long.
L'architecture JS est plus rapide. Il n'y a pas de processus à lancer. Le prix à payer est la programmation asynchrone, qui est plus compliquée... même si « await » simplifie beaucoup les choses.
import express from 'express';
const app = express();
app.listen(3000, () => {
console.log('Notre app express écoute sur le port 3000')
});
app.get('/', (requete, reponse) => {
console.log('Requete GET / reçue');
reponse.send('Hello World!')
})
app.get('/bonjour', (requete, reponse) => {Ceci dit à Express:
console.log('Requete GET /bonjour reçue');
reponse.send('Bonjour à toi!')
})
En JS, il y a de nombreuses (beaucoup trop) manières de parcourir un objet.
Prenons un exemple:
let exemple={On voudrait faire une boucle pour parcourir soit les clés ("Joe", "Leila", "Tom" ), soit les valeurs (123,543,91), soit les deux (clés et valeurs).
"Joe": 123,
"Leila": 543,
"Tom": 91,
};
Object.values(...) renvoie un tableau JS (et non pas un objet) avec toutes les valeurs.
On peut alors parcourir ce tableau avec une boucle for...of:
for(const valeur of Object.values(exemple)){
console.log(valeur);
}
Object.entries(...) renvoie un tableau JS (et non pas un objet) constitué de petits tableaux contenant 2 éléments :une clés et une valeurs:
Object.entries(exemple) renvoie:
[
["Joe", 123],
["Leila", 543],
["Tom", 91],
]
On peut alors parcourir ce tableau avec une boucle for...of:
for(const [cle,valeur] of Object.entries(exemple)){
console.log(cle,valeur);
}
Vue.js est un framework frontend pour construire des pages web ayant beaucoup d'interactions.
Vue.js a des points communs avec React, Angular, Svelte et d'autres.
Ces frameworks sont souvent utilisés pour construire des « single page applications », c'est à dire des sites où l'utilisateur reste sur la même page et toutes les interactions sont gérées par JS et Ajax.
Dans l'approche classique, la navigation se fait en cliquant sur des liens ou en appuyant sur des boutons de formulaires. A chaque fois une nouvelle page (P) est visitée. La page visitée peut avoir des interactions JS, mais, en général, elles ne changent pas l'essentiel de l'affichage de la page. De temps en temps une interaction AJAX (A) peut venir enrichir cette navigation.
À l'autre extrême, dans une SPA, une seule page (P) est chargée et l'utilisateur ne la quitte pas. L'utilisateur interagît avec cette page de manière complexe et l'affichage peut changer complètement. Ces interactions sont gérées en JS avec des appels AJAX (A) fréquents.
Dans une SPA, certaines fonctionnalités du navigateur, comme le bouton retour arrière / avant et l'ouverture d'un lien dans un autre onglet,...), posent problème.
Une grande différence entre Vue.js et ce qu'on a fait précédemment, est l'approche « déclarative ».
Jusqu'à présent, on partait d'une page HTML et on la modifiait à partir du JS quand l'utilisateur interagissait (click, clavier, ...).
En pratique, une page HTML est généralement construite (par exemple en PHP) à partir de données.
Quand les interactions JS deviennent importantes, on se retrouve à reconstruire en JS une bonne partie du HTML à partir de données modifiées:
données => HTML <= modification des données et du HTML par JS
On construit donc le HTML de deux manières très différentes. Ça conduit à des incohérences. Par ailleurs, modifier du HTML en JS est beaucoup plus compliqué que d'écrire du HTML.
Dans une approche déclarative, on décrit une seule fois comment construire le HTML à partir des données. Quand les données changent, la page se met à jour automatiquement. On a donc:
donnés => HTML
Prenons un exemple très très simple:
La donnée est « age », et on on veut la faire passer de 25 à 26 quand l'utilisateur clique sur un bouton.
PHP:
$age=25;
echo '<h1>Age: '.$age.'<h1>';
echo '<input type="button" value="bouton"/>';
Ensuite, en JS:
document.querySelector('input').addEventListener('click',()=>{
document.querySelector('h1').textContent='Age:'+26;
});
En JS, on a voulu changer l'age, on a donc du retrouver le h1 et le modifier. Ce code JS est compliqué par rapport à l'écriture du HTML.
Si les propriétaires veulent maintenant afficher « Mon age est: 25 » au lieu de « Age: 25 », le développeur va devoir changer à la fois le code PHP et le code JS. S'il oublie l'un où l'autre, ce sera incohérent.
...
let age=ref(25);
function changer(){
age.value=26;
}
template:
<h1>Age: {{age}}</h1>
<input type="button" value="bouton" @click="changer"/>
Quand l'utilisateur clique sur le bouton, la fonction « changer » change uniquement la variable age. La page se met automatiquement à jour. Aucun besoin d'écrire du JS compliqué pour modifier le DOM. Si les propriétaires de la page veulent maintenant afficher « Mon age
est: 25 » au lieu de « Age: 25 », le développeur va juste modifier le template, ce qui est relativement simple.
Tapez la commande
$ npm run dev
Ceci lance un serveur de développement et affiche son URL : http://locahost:5173 (Attention: ce numéro de port - 5173 - peut changer)
Ouvrez l'URL affichée.
Et voila ! Votre première App Vue.js 😊
Avec ce serveur de développement, appelé « vite », quand vous modifiez le code, la page se recharge toute seule!
App.vue est un fichier « .vue » avec trois parties:
En Vue.js, votre application est construite avec des « composants ». Chaque fichier « .vue » est un composant.
Il contient tout ce qu'il faut pour que ce composant marche (JS, HTML, CSS).
Dans un <template>, on veut écrire du HTML qui affiche des données.
Il y a deux types d'endroits où on peut afficher des variables et même des expressions JS:
Dans le contenu (« texte ») d'une balise, on utilise {{ ... code JS ... }} :
<template>
<h1>Hello {{nom}}, {{1+1}}, {{nom+123}}</h1>
</template>
Essayez dans App.vue
Dans un attribut d'une balise, on utilise « : » devant le nom de l'attribut. La valeur de l'attribut est alors considérée comme du JS:
<template>Essayez dans App.vue
<h1>Hello {{nom}}</h1>
<input type="button" :value="nom" />
<input type="button" :value="'hello'+nom"/>
</template>
Pour gérer les événements on ajoute un attribut avec un « @ » suivi du nom de l'événement.
Pour l'instant, la valeur de l'attribut sera juste le nom de la fonction.
<script setup>
let nom='Tom';
function exemple(){
console.log('ca marche!');
}
function exemple2(e){
console.log('input',e.target.value);
}
</script>
<template>
<h1 @click="exemple">Hello {{nom}}</h1>
<input @keyup="exemple2" />
</template>
Essayez dans App.vue
Dans cet exemple, l'affichage de « Hello Tom » n'est pas mis à jour quand on fait nom='Leila'
<script setup>
let nom='Tom';
function exemple(){
console.log('click');
nom='Leila';
}
</script>
<template>
<h1 @click="exemple">Hello {{nom}}</h1>
</template>
En effet, Vue.js ne sait pas que la variable « nom » a changé. Il ne sait donc pas qu'il faut mettre à jour l'affichage.
Pour que Vue.js le sache, on doit crée des variables réactives. C'est un concept important.
<script setup>
import {ref,reactive} from 'vue';
let nom=ref('Tom');
function exemple(){
console.log('click');
nom.value='Leila';
}
</script>
<template>
<h1 @click="exemple">Hello {{nom}}</h1>
</template>
Essayez dans App.vue. Cliquez sur le <h1>
<script setup>
import {ref,reactive} from 'vue';
let user=reactive({nom:'Tom',age: 25});
function exemple(){
console.log('click');
user.nom='Leila';
}
</script>
<template>
<h1 @click="exemple">Hello {{user.nom}}, Age: {{user.age}}</h1>
</template>
Essayez dans App.vue. Cliquez sur le <h1>Pour l'instant, on a mis le nom d'une fonction dans l'attribut événement (@click, @keyup, ...).
function touche(){ ... }
...
<input @keyup="touche" />
On peut aussi y mettre directement du code JS. On peut y utiliser des variables ref() sans « .value ». Par exemple:
<input type="button" @click="c++" />Le code JS peut être une fonction fléchée:
<input @keyup="e=>nom=e.target.value" />
Nom: <input @keyup="nom=$event.target.value" />On ne peut pas, malheureusement, utiliser directement console.log, ni alert:
Nom: <input @keyup="console.log('ceci ne sera pas affiché')" />
Voici un exemple complet. Essayez-le.
<script setup>
import {ref,reactive} from 'vue';
let c=ref(0);
let nom=ref('Tom');
</script>
<template>
<p>Le joueur {{nom}} a {{c}} points.</p>
<p>Le joueur {{nom}} a fini le niveau 3.</p>
<p>Le joueur {{nom}} est classé 4e.</p>
Nom: <input @keyup="e=>nom=e.target.value" />
Nom: <input @keyup="nom=$event.target.value" />
<input type="button" @click="c++" value="cliquer"/>
</template>
<style scoped>
</style>
On veut souvent afficher une suite de choses dans un template. On a donc besoin de boucles.
Vue.js permet de faire différentes sortes de boucles en utilisant l'attribut « v-for ».
Voyons une boucle très simple:
<template>
<span v-for="i in 10">Hello {{i}}</span>
</template>
Cette boucle crée 10 <span>. Attention, la valeur « i » va de 1 à 10 (et non pas de 0 à 9).
L'attribut « v-if » permet d'inclure un élément HTML de manière conditionnelle.
Par exemple:
<script setup>
let condition=true;
</script>
<template>
<p v-if="condition">Bonjour!</p>
</template>
Le paragraphe ce sera inclus que si la variable condition est vraie.
On peut, bien entendu, mettre du JS dans la condition:
<script setup>
let age=25;
</script>
<template>
<p v-if="age>=18">Bonjour!</p>
</template>
On a souvent besoin d'indiquer plusieurs classes sur une balise dans le template.
La présence de ces classes dépend souvent de conditions.
On peut alors utiliser un objet JS pour indiquer quelles classes inclure.
Cet exemple:
<template>
<p :class="{urgent: true, important: false}">Bonjour</p>
</template>
Donnera
<p class="urgent">Bonjour</p>