Académique Documents
Professionnel Documents
Culture Documents
Sommaire
Contexte :...............................................................................................................................................3
Problématique :.....................................................................................................................................3
Plan d’action :........................................................................................................................................3
I. Définition.......................................................................................................................................3
2
Dario VECCHIO
PROSIT
Contexte :
On veut ajouter des applications en Node JS sur notre site. (Pierre)
on cherche à trouver une solution pour ne pas agir en mode synchronisé, mais en mode
asynchronisé. (Tristan)
Après avoir observé des sites de e-commerce tomber durant la période du black Friday, on
cherche à trouver une solution pour ne pas agir en mode synchronisé.
Problématique :
- Comment mettre en place un site tournant sur Node JS ?
Contraintes :
- Notre site
- Node JS
Besoins :
- connaitre le fonctionnement d’un serveur classique
Plan d’action :
- Mode synchronisé / asynchronisé
- Node JS
o L’environnement
Comment l’installer ?
o Comment les relier entre nos différents éléments
o Comment faire un serveur http en Node JS
- React
Réalisation :
3
Dario VECCHIO
PROSIT
I. Définition
- Mode synchronisé
- Evènement
- .jsx (extension)
- Client/serveur
- Node JS
- Framework
- Elément bloquant
- trafic
setTimeout(function () {
console.log("Deuxième")
}, 1000);
console.log("Troisième")
// ==> Résultat:
// Premier
// Troisième
// Deuxième
La fonction "setTimeout" prend deux paramètres : une fonction (appelée callback) et un entier
qui est le nombre de millisecondes à attendre avant d’appeler le callback. Étant donné que
"setTimeout" est asynchrone, le script va continuer son déroulement jusqu’à ce que le callback
soit appelé.
4
Dario VECCHIO
PROSIT
Les callbacks
Un callback est une fonction de rappel passée en paramètre d’une autre fonction et qui sera
appelée à la fin de cette dernière pour effectuer une autre opération . Plus clairement, utiliser un
callback se résume à dire "j’exécute la fonction B une fois que la fonction A est terminée".
Un petit exemple pour illustrer ce procédé :
function helloWorld() {
console.log("Hello World !");
}
setup("Nginx", helloWorld)
// ==> Résultat:
// Installation de Nginx
// Hello World !
Ici la fonction "helloWorld" est passée en paramètre de la fonction "setup", c’est bien un
callback. Les callbacks sont beaucoup utilisés pour faire des requêtes HTTP, pour traiter des
fichiers (avec NodeJS) ou encore dans la déclaration des événements, comme le montre cet
exemple :
document.getElementById("monBouton").addEventListener("click", function() {
alert("Vous avez cliqué sur le bouton !")
});
Depuis la 6e version du standard EcmaScript (2015), une nouvelle syntaxe a été mise à
disposition pour définir une fonction, elle s’appelle la fonction fléchée.
Elle se construit sous cette forme :
myFunction = () => {
console.log("Fonction fléchée !")
}
Les fonctions fléchées sont principalement utilisées comme des callbacks.
Si l’on reprend l’exemple précédent avec cette syntaxe plus simpliste, cela nous donne :
document.getElementById("monBouton").addEventListener("click", () => {
alert("Vous avez cliqué sur le bouton !")
});
Les Promises
L’objet "Promise" permet de faire des opérations asynchrones plus simplement . Son
fonctionnement est simple : on lance une opération de manière asynchrone dont on attend un
résultat dans le futur (promesse, comme son nom l’indique).
Elle se construit de cette manière :
new Promise(function(resolve, reject) {
5
Dario VECCHIO
PROSIT
setTimeout(function () {
resolve("Promise résolue")
}, 1000);
});
La Promise prend en paramètre une fonction qu’elle exécute immédiatement et qui prend elle
même deux paramètres : une fonction de callback qui permet de signaler que la promesse a été
tenue (tout s’est bien passé donc on retourne le résultat) et une deuxième fonction de callback
qui permet cette fois de signaler que la promesse est en échec (possibilité de retourner une
erreur).
Un avantage de la Promise est qu’elle peut être dans 3 états différents :
Pending (en attente): l’opération est en cours d’exécution.
Fulfilled (satisfaite) : l’opération est terminée et a réussie.
Rejected (rejetée) : l’opération est terminée et a échouée.
On peut donc effectuer une tâche asynchrone, attendre la fin de celle-ci sans bloquer le fil
d’exécution du processus puis continuer le déroulement de notre script.
Voici un exemple montrant comment traiter la donnée d’une requête HTTP
function getHTTP(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function() {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
}
};
xhr.open("GET", url);
xhr.send();
});
}
getHTTP("https://www.google.fr")
.then(function (response) {
// On récupère le resultat de la requête dans la varible "response"
console.log(response)
})
La fonction "getHTTP" va retourner une Promise qui elle-même va retourner le résultat de la
requête HTTP, il nous suffi t de chaîner la fonction avec ".then()" pour récupérer le résultat . Cette
fonction nous permet de réduire le nombre de lignes et de simplifier la façon de faire des
requêtes asynchrones (ici dans un navigateur Web avec l’objet "XMLHttpRequest").
Vous l’aurez peut-être remarqué, si la requête ne fonctionne pas, la Promise ne se terminera
jamais car nous ne prenons pas en charge les erreurs.
6
Dario VECCHIO
PROSIT
Pour gérer les erreurs, la façon la plus simple est d’utiliser la méthode "catch" de l’objet
Promise. Comme "then", elle prend en paramètre un callback qui sera retourné par la Promise, à
la différence près qu’elle prendra uniquement le callback que va rejeter la Promise (si il y a une
erreur).
Voici le code précédent, adapté pour prendre en compte les erreurs :
function getHTTP(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function() {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
}
else {
reject({
status: this.status,
statusText: this.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: this.statusText
});
};
xhr.open("GET", url);
xhr.send();
});
}
getHTTP("https://www.google.fr")
.then(function (response) {
// On récupère le resultat de la requête dans la varible "response"
console.log(response)
})
.catch(function (error) {
// On affiche le code de retour de la requête
console.log(error.status)
// Puis le texte du status
console.log(error.statusText)
})
7
Dario VECCHIO
PROSIT
Un problème qui arrive assez souvent en JavaScript, est le fait d’écrire des fonctions imbriquées
les unes dans les autres, plus communément appelé le "callback hell".
Exemple :
getHTTP("https://www.google.fr")
.then(function (googleResponse) {
getHTTP("https://www.supinfo.com")
.then(function (supResponse) {
getHTTP("https://example.com")
.then(function (exampleResponse) {
console("End of callBack hell !")
})
.catch(function (thirdError) {
console.log(thirdError.status)
}) })
.catch(function (secondError) {
console.log(secondError.status)
})})
.catch(function (firstError) {
console.log(firstError.status)
})
Ce bout de code fait 3 requêtes les unes après les autres en prenant en compte les erreurs pour
chaque requête. Comme vous pouvez le remarquer ce n’est pas très lisible (voir pas du tout),
alors pour palier à ce problème deux solutions s’offrent à nous :
Exécuter toutes les requêtes en même temps
Chaîner les Promises
Pour la première solution nous avons besoin de la méthode "Promise.all()", qui prend en
paramètre un tableau de Promise et qui retournera une unique Promise, une fois que la liste de
Promises sera traitée :
Promise.all([getHTTP("https://www.google.fr"), getHTTP("https://www.supinfo.com"),
getHTTP("https://example.com")])
.then(function (data) {
var googleResponse = data[0];
var supResponse = data[1];
var exampleResponse = data[2];
})
.catch(function (error) {
console.log(error.status)
})
Cette solution est la plus courte (en termes de ligne) mais elle ne peut pas être utilisée dans le
cas où l’on veut faire les appels un à un pour utiliser la valeur de la précédente requête avant de
faire la suivante. Cela tombe bien, la seconde solution est là pour ça :
getHTTP("https://www.google.fr")
.then(function (googleResponse) {
console.log(googleResponse);
8
Dario VECCHIO
PROSIT
return getHTTP("https://www.supinfo.com")
})
.then(function (supResponse) {
console.log(supResponse);
return getHTTP("https://example.com")
})
.then(function (exampleResponse) {
console.log("end");
})
.catch(function (error) {
console.log(error.status)
})
Ici les Promises sont chaînées. Lors du retour de la première Promise nous pouvons alors faire un
traitement spécifique sur le résultat, on retourne une autre Promise puis une fois celle-ci résolue
on passe à la méthode "then" suivante et ainsi de suite.
Dans les deux exemples précédents, la méthode "catch" n’était présente qu’une seule fois, elle
permet d’intercepter toutes les erreurs des précédentes Promises et également de garantir une
syntaxe simple et plus compréhensible (on économise également quelques lignes de code).
Il est également possible d’ajouter un ".then()" supplémentaire à la fin du script qui permet
d’exécuter une fonction dans tous les cas, peu importe le résultat des Promise précédentes :
getHTTP("https://www.google.fr")
.then(function (googleResponse) {
return getHTTP("pageInexistan.te")
})
.then(function (res) {
console.log(res);
})
.catch(function (error) {
console.log(error.status)
})
.then(function () {
console.log("Opérations terminées");
})
Async / Await
"Async" et "Await" sont deux opérateurs issus de l’EcmaScript 7 (2016), s’inspirant d’autres
langages comme le Python ou le C#, qui ont beaucoup réjoui les développeurs JavaScript à leur
arrivé. Avant que ces opérateurs soient standardisés, nous étions obligé d’utiliser de multitudes
de callbacks qui rendaient souvent le code peu lisible. Maintenant, nous pouvons faire des
appels à des fonctions asynchrone en utilisant la syntaxe d’un code synchrone, ce qui rend
visuellement plus logique et plus simple à comprendre.
Voici un exemple pour illustrer cet avantage :
async function main() {
var googleResponse = await getHTTP("https://www.google.fr")
console.log(googleResponse);
9
Dario VECCHIO
PROSIT
main()
Comme on peut le voir, il n’y a aucun callback, et le code se lit très facilement.
Le fonctionnement technique de ces opérateurs est assez simple :
On place "async" en premier lieu lorsque l’on déclare une fonction pour préciser que
celle-ci est asynchrone.
On place "await" juste avant d’appeler une fonction asynchrone, ce qui aura pour but
d’attendre la réponse de cette dernière.
Tout ça n’est pas magique. En réalité, Async va automatiquement transformer la fonction en une
Promise et va résoudre celle-ci avec le résultat retourner par "return". Grâce à Async, on a accès
à Await dans la fonction, qui lui permet de forcer le reste du code à attendre que la Promise soit
résolue et qu’elle retourne un résultat (ou une erreur).
Await fonctionne seulement dans une fonction précédée de Async et permet d’attendre
uniquement une Promise.
Exemple avec un callback :
async function sleep() {
await setTimeout(function () {
return "wake up !"
}, 1000);
}
sleep().then(function(res) {
console.log(res);
})
Le code ci-dessus n’affi chera rien à l’écran. Comme Await n’attend pas les callbacks, la fonction
"sleep" va se terminer directement sans rien retourner.
Pour obtenir une fonction "sleep" comme celle présente en Bash, nous pourrions faire comme
cela :
function sleep(time){
return new Promise(function(resolve) {
setTimeout(function () {
resolve()
}, time);
})
}
async function main() {
console.log("Sleeping..");
await sleep(1000)
return "wake up !"
}
10
Dario VECCHIO
PROSIT
main().then(function(res) {
console.log(res);
})
Async et Await ne remplacent pas les Promises, ils permettent uniquement de simplifier l’appel
de celles-ci.
La bonne pratique lorsque l’on utilise ces opérateurs, est de découper toutes les tâches
asynchrones en différentes fonctions qui retournent une Promise. Ensuite, il ne reste plus qu’à
appeler ces fonctions avec Await dans la partie principale du code, et adieu les callbacks !
Pour prendre en charge les erreurs des fonctions utilisant Async/Await, on utilise simplement le
bon vieux "try/catch" :
function errorFunction(){
return new Promise(function(resolve, reject) {
setTimeout(function () {
reject("There is a bug !")
}, 1000);
})
}
main()
Dans le cas si dessus, nous pourrions également prendre en compte les erreurs lorsque l’on
appelle la fonction "main" en chaînant avec la méthode "catch" :
async function main() {
await errorFunction()
}
main()
.then(function () {
console.log("done");
})
.catch(function (error) {
console.log(error)
})
Il est possible d’exécuter une liste de Promises et d’attendre le retour de celles-ci avant de
continuer le déroulement du script, alors comment adapter ce procédé avec les opérateurs
Async/Await ?
11
Dario VECCHIO
PROSIT
exemple :
async function main() {
var [googleResponse, supResponse] = await
Promise.all([getHTTP("https://www.google.fr"), getHTTP("https://www.supinfo.com")])
console.log(googleResponse);
console.log(supResponse);
}
main()
On précède simplement la méthode "Promise.all()" (qui retournera un tableau de résultats) avec
"await".
Conclusion
Il est important de se rappeler que les opérateurs Async/Await utilisent les Promises, ne les
remplacent pas et permettent simplement de faciliter l’écriture et la compréhension du code.
Avant de commencer le développement d’un nouveau site Web qui utilise les Promises, il est
conseillé de vérifier si la version des navigateurs ciblés est compatible (il en est de même pour
NodeJS).
Il est ainsi possible de dire que JavaScript est un peu plus qu'un langage, c'est aussi une architecture.
12
Dario VECCHIO
PROSIT
III. Node Js
Node.js nous permet d'utiliser le langage JavaScript sur le serveur... Il nous permet donc
de faire du JavaScript en dehors du navigateur !
Node.js bénéficie de la puissance de JavaScript pour proposer une toute nouvelle façon de
développer des sites web dynamiques. Node.js nous permet d'utiliser le langage JavaScript
sur le serveur... Il nous permet donc de faire du JavaScript en dehors du navigateur !
13
Dario VECCHIO
PROSIT
Node.js bénéficie de la puissance de JavaScript pour proposer une toute nouvelle façon de
développer des sites web dynamiques.
Node.js nous permet d'utiliser le langage JavaScript sur le serveur... Il nous permet donc de faire du
JavaScript en dehors du navigateur !
Node.js bénéficie de la puissance de JavaScript pour proposer une toute nouvelle façon de
développer des sites web dynamiques.
Node.js offre un environnement côté serveur qui nous permet aussi d'utiliser le langage JavaScript
pour générer des pages web. En gros, il vient en remplacement de langages serveur comme PHP,
Java EE, etc.
Un serveur de Chat
... et de façon générale n'importe quelle application qui doit répondre à de nombreuses
requêtes rapidement et efficacement, en temps réel
Mais au fait, connaissez-vous la différence entre un code bloquant et un code non bloquant ?
Hmmm, un peu d'explications ne peuvent pas faire de mal je vois !
Télécharger un fichier
Afficher le fichier
14
Dario VECCHIO
PROSIT
Les actions sont effectuées dans l'ordre. Il faut lire les lignes de haut en bas :
Télécharger un fichier
3. Dès que le téléchargement est terminé, le programme effectue les actions qu'on lui avait
demandées : il affiche le fichier
});
On dit que c'est une fonction anonyme. Mais on pourrait décomposer ce code comme
ceci, le résultat serait identique :
15
Dario VECCHIO
PROSIT
});
request('http://www.site.com/fichier.zip', callback);
Node.js est monothread, contrairement à Apache. Cela veut dire qu'il n'y a qu'un seul processus,
qu'une seule version du programme qui peut tourner à la fois en mémoire.
Cela est dû à la nature "orientée évènements" de Node.js. Les applications utilisant Node ne restent
jamais les bras croisés sans rien faire. Dès qu'il y a une action un peu longue, le programme redonne
la main à Node.js qui va effectuer d'autres actions en attendant qu'un évènement survienne pour
dire que l'opération est terminée.
16
Dario VECCHIO
PROSIT
res.writeHead(200);
});
server.listen(8080);
C'est en quelque sorte le "code minimal" pour un projet Node.js. Placez-le dans un fichier
que vous appellerez serveur.js (par exemple).
17
Dario VECCHIO
PROSIT
Notez que vous pouvez faire ça en deux temps comme je vous l'avait dit. La fonction à
exécuter est la fonction de callback. On peut la définir avant dans une variable et
transmettre cette variable à createServer(). Ainsi, le code ci-dessous est strictement
identique au précédent :
res.writeHead(200);
Il y a des fonctions de callback de partout, et en général elles sont placées à l'intérieur des arguments
d'une autre fonction.
fermer la fonction de callback avec une accolade, puis de fermer les parenthèses d'appel de la
fonction qui l'englobe, puis de placer le fameux point-virgule. C'est pour ça que vous voyez les
symboles});à la dernière ligne
La fonction de callback est donc appelée à chaque fois qu'un visiteur se connecte à notre site. Elle
prend 2 paramètres :
La requête du visiteur (reqdans mes exemples) : cet objet contient toutes les informations
sur ce que le visiteur a demandé. On y trouve le nom de la page appelée, les paramètres, les
éventuels champs de formulaires remplis...
La réponse que vous devez renvoyer (resdans mes exemples) : c'est cet objet qu'il faut
remplir pour donner un retour au visiteur. Au final, res contiendra en général le code HTML
de la page à renvoyer au visiteur.
Que dit la norme HTTP ? Que le serveur doive indiquer le type de données qu'il s'apprête à envoyer
au client. Eh oui, un serveur peut renvoyer différents types de données :
18
Dario VECCHIO
PROSIT
Du HTML : text/html
Du CSS : text/css
etc.
On doit indiquer à la suite de la réponse du serveur Node.js le type MIME de la réponse. Pour
HTML, ce sera donc :
Pour récupérer la page demandée par le visiteur, on va faire appel à un nouveau module de
Node appelé "url". On demande son inclusion avec :
url.parse(req.url).pathname;
Voici un code très simple qui nous permet de tester ça :
console.log(page);
res.write('Bien le bonjour');
res.end();
});
19
Dario VECCHIO
PROSIT
server.listen(8080);
Le problème, c'est qu'on vous renvoie toute la chaîne sans découper au préalable les
différents paramètres. Heureusement, il existe un module Node.js qui s'en charge pour
nous : querystring !
Incluez ce module :
IV. React.js
React n’est pas à proprement parler un framework mais se présente comme une bibliothèque
JavaScript pour créer des interfaces utilisateurs. React est la réponse de Facebook à un problème
récurrent : créer des interfaces réutilisables et stateful.
React est basé sur virtual-dom : un composant React ne crée pas de HTML mais une représentation
sous forme d’objets et de nœuds de ce à quoi le HTML final doit ressembler. Virtual-dom va prendre
en compte cette représentation, la comparer au DOM réel et en déduire les opérations minimales à
exécuter pour que le DOM réel soit conforme au virtuel. C’est grâce à cet outil que React peut partir
du principe qu’il est plus simple de “remplacer” toute l’interface quand elle doit être modifiée plutôt
que de modifier au fur et à mesure le DOM comme jQuery ou AngularJS pouvaient le faire.
L’intérêt de cette approche est assez simple. On reproche souvent à JavaScript d’être lent alors que
c’est DOM qui l’est. Avoir une représentation sous forme d’arbre en JavaScript permet de réaliser
beaucoup plus d’opérations, d’utiliser les meilleurs algorithmes de comparaison d’arbres et, cerise
sur le gâteau, de faire toutes les modifications du DOM en une opération plutôt qu’au fur et à
mesure. Virtual-dom est également bien plus facile à mettre à jour et à améliorer que les différentes
implémentations de DOM dans les navigateurs.
Prenons un exemple pour expliquer tout ça. Voici le « hello world » de React (en ES2015)
20
Dario VECCHIO
PROSIT
render() {
});
ReactDOM.render(React.createElement(Hello), document.getElementById("app"));
L’appel ReactDOM.render va exécuter la fonction render et récupérer un arbre DOM virtuel, le
comparer à l’arbre DOM contenu dans l’élément qui a pour identifiant app et y ajouter le
texte “hello world”. React.createElement prend en paramètre l’élément HTML, un objet
d’attributs et des enfants (tous les paramètres après le 2ème seront des enfants). Rien
d’extraordinaire pour le moment mais ajoutons un peu d’interactivité (et oublions
les import par simplicité).
getInitialState() {
return {
hello: "world"
};
},
21
Dario VECCHIO
PROSIT
handleInputChange(e) {
this.setState({
hello: e.target.value
});
},
render() {
return React.createElement(
'div',
null,
React.createElement(
'input',
value: this.state.hello,
onChange: this.handleInputChange
},
null
),
React.createElement(
22
Dario VECCHIO
PROSIT
'div',
null,
);
});
ReactDOM.render(React.createElement(Hello), document.getElementById("app"));
Revenons au concept de stateful : ici, notre composant a un état (state) dont la valeur par
défaut est définie dans getInitialState . On peut y accéder dans render avec this.state et on
l’assigne à la fois en valeur à notre input et en texte. On utilise l’attribut onChange (comme en
HTML) sur notre input pour lier l’évènement à une fonction du composant qui a la charge de
mettre à jour l’état par la méthode setState et c’est là que le virtual-dom entre en jeu.
Appeler setState va provoquer un rendu (render), dont la valeur de retour va être différente
du DOM actuel et les changements seront donc appliqués.
JSX
Passons à un autre élément important de React : les appels à React.createElement sont très vite
verbeux et pas forcément évidents à lire. La solution classique à ce problème est JSX : une
extension de la syntaxe JavaScript qui ressemble au HTML. Le même exemple ci dessus avec
JSX donne :
getInitialState() {
return {
23
Dario VECCHIO
PROSIT
hello: "world"
};
},
handleInputChange(e) {
this.setState({
hello: e.target.value
});
},
render() {
return (
<div>
<div>hello {this.state.hello}</div>
</div>
);
});
24
Dario VECCHIO
PROSIT
Cependant JSX nécessite une phase de transpilation : elle peut être réalisée avec Babel,
directement avec la version 5, ou via le preset “React” pour Babel 6. Babel 6 a en effet été
totalement réécrit pour être modulaire et ne fait aucune transformation par défaut : il y a
maintenant un plugin par transformation et les presets sont un regroupement de ces
transformations. Le preset ES2015 par exemple permet les transformations qui
correspondent aux nouveautés de la dernière version de JavaScript.
render() {
});
25
Dario VECCHIO
PROSIT
De plus, depuis React 0.14 (aka React 14), il est possible de réaliser des composants sous
forme d’une simple fonction, essentiellement la fonction render . C’est ce qu’on appelle la
plupart du temps des functional components. Cette fonction prend en paramètre l’objet
représentant les propriétés (props). En utilisant le destructuringd’ES2015, on peut donc
transformer le composant Hello de cette façon :
Et à partir de là, on peut faire remonter les interactions vers un composant de plus haut
niveau pour composer avec cet élément.
getInitialState() {
return {
26
Dario VECCHIO
PROSIT
hello: "world"
};
},
handleInputChange(e) {
this.setState({
hello: e.target.value
});
},
render() {
return (
<div>
</div>
});
27
Dario VECCHIO
PROSIT
Utiliser en priorité les props est conseillé pour la réutilisabilité : sans état, un composant est
plus simple à prendre en main et son rendu est parfaitement prévisible ; donc plus simple à
tester et à débugger. Cependant, il n’est pas nécessaire de n’avoir qu’un composant qui gère
toutes les données de l’application. Un module précis (ex : un accordéon) pourrait stocker
dans son état quelques informations qui ne concernent que lui. De la même façon, il vaut
mieux n’utiliser l’état que pour des informations d’interface ou des petites données. Si vos
besoins venaient à être plus importants, il vaut mieux s’orienter vers une bibliothèque
dédiée basée sur Flux ou Redux.
Dom Virtuel
React a inauguré la notion de DOM virtuel : React lui-même ne manipule pas directement le DOM du
navigateur. Cela serait coûteux en performance et empêcherait de décliner cette approche sur
d’autres supports tels que les applications mobiles natives, les terminaux textuels, les fichiers PDF,
etc.
À la place, React nous fait décrire un DOM virtuel, absolument distinct du DOM des navigateurs. Au
moment venu il réconcilie ce DOM virtuel avec la couche de rendu réelle (par exemple, le DOM du
navigateur, ou, si on est côté serveur, la production du texte HTML à renvoyer côté client), en
prenant soin de minimiser le nombre d'opérations nécessaires.
28
Dario VECCHIO