Écrire du JavaScript moderne
ECMAScript (abrégé ES) est un standard dont les spécifications sont mises en œuvre dans le langage JavaScript
Le TC39 est un comité technique au sein d'ECMA qui s'occupe de discuter des futures fonctionnalités à incorporer dans le langage JS.
La liste des fonctionnalités est publiquement libre :
https://github.com/tc39/proposals/blob/master/finished-proposals.md
💡
L'ensemble des fonctionnalités du langage JavaScript que nous verrons dans ce cours font partie de ES2015 et >
💔
Il n'est pas nécessaire de les apprendre par cœur, mais de savoir les identifier et les utiliser correctement !
📝
Pour chaque fonctionnalité présentée, il y aura un petit exercice pratique suivi d'une correction.
👩💻👨💻
C'est parti !
(ES2015)
Avant ES2015, la seule manière de déclarer des variables en JS était d'utiliser le mot-clé var
var prenom = 'John';
var nom = 'DOE';
var age = 42;
var isStudent = false;
« Oui et … quel est le problème ? 🙄 »
Un premier problème de var est qu'on peut
re-déclarer la même variable plusieurs fois.
var prenom = 'Marie';
// …
var prenom = 'Jean'; // Tout va bien …
C'est sale, et ça augmente surtout le risque
d'erreurs à long terme
Autre soucis avec var :
la persistance de la variable.
Il est fréquent d'utiliser des variables uniquement pour
les besoins d'un bloc for ou d'un if
var tableau = ['jason', 'andrew', 'julian'];
for (var i = 0; i < tableau.length; i++) {
// …
}
console.log(i); // Affiche: 3
La variable "i" existe encore en mémoire après la fin de la boucle,
alors qu'elle n'a plus aucune utilité 😐
Autre problème lors d'une boucle : la même variable est ré-utilisée à chaque tour, rendant compliquée la gestion de code asynchrone :
// Sélectionne tous les éléments <button> du HTML
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log('Clic sur buttons[' + i + ']');
}
}
Non, le résultat n'est pas celui auquel on s'attend ...
Une déclaration de variable avec var est "hoistée"
et initialisée à "undefined",
d'où un nouveau problème :
console.log(name); // undefined
var name = 'Jason Statham';
Ce code devrait logiquement produire une
ReferenceError,
mais il affiche pourtant undefined
Le scope définit l'endroit où les variables du programme sont accessibles.
// scope global
var date = new Date();
function getYear () { // scope de la fonction
var year = date.getFullYear();
return year;
}
getYear();
console.log(date); // ✔
console.log(year); // ❌ ReferenceError
La variable year est définie dans le
scope de la fonction,
et par conséquent n'est pas accessible à l'extérieur de
celle-ci.
Une variable déclarée avec var est accessible soit :
Ce sont les deux seuls scopes pour une var …
var est à l'origine de plusieurs problèmes et ne devrait plus être utilisé dans une base de code JS moderne
Globalement, on remplace juste var par
let :
let prenom = 'John';
let nom = 'DOE';
let age = 42;
let isStudent = false;
La variable est locale au scope du bloc où elle est déclarée
for (let i = 0; i < tableau.length; i++) {
// …
}
/* La variable "i" n'existe plus ici, car elle était limitée
au scope du bloc for() */
C'est une bonne chose car i n'a plus aucune utilité après la boucle
Comme un scope distinct est créé pour chaque itération, il n'y a plus de problème à utiliser un code asynchrone pendant la boucle
// Sélectionne tous les éléments <button> du HTML
let buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log('Clic sur buttons[' + i + ']');
}
}
Démo : https://jsbin.com/lagoxow/edit?html,js,console,output
De plus, avec let, il n'est plus possible de re-déclarer la
même variable une seconde fois dans le programme
let prenom = 'Jean';
// …
let prenom = 'Marie';
// ❌ SyntaxError: 'prenom' has already been declared
C'est une bonne chose car cela évite d'écrire du mauvais code à long terme
Enfin, une déclaration de variable avec let est toujours "hoistée" mais non
initialisée
console.log(name); // ❌ ReferenceError: name is not defined
let name = 'Jason Statham';
Ce code produit bien une ReferenceError comme on s'y attend.
Techniquement, on tente ici d'accéder à "name" lorsqu'elle
est
dans la temporal dead zone (TDZ)
Il n'y a plus vraiment d'avantage à utiliser var en
JS moderne.
Généralement, on utilise plutôt let 👍
Une constante représente une valeur accessible en lecture seule
On la déclare de la même façon qu'une variable, mais avec le mot-clé const
Exemple :
// Ré-assignation d'une variable :
let day = 15;
day = 16; // ✔ Ok
// Tentative de ré-assignation d'une constante :
const year = 2020;
year = 2021; // ❌TypeError: Assignment to constant variable.
Si la constante référence un objet JS, les propriétés de cet objet peuvent toutefois être mutées :
const person = { name : 'Steve' , age : 39 };
/* Ici, seules les propriétés de l'objet changent,
et non l'objet lui-même */
person.age++; // ✔
person.name = 'Tim'; // ✔
person.lastname = 'Cook'; // ✔
person = {}; // ❌ (Réassignation d'une constante)
Concernant le scope, les même règles s'appliquent pour const et pour
let
On utilise généralement une const si on sait que notre valeur n'aura pas à changer plus tard dans le programme (ce qui est souvent le cas pour des objets que l'on fait muter).
Si on sait que la valeur pourra être modifiée, on utilisera alors un let
Lisez la documentation (en Français)
(ES2015)
Les template strings sont un nouveau moyen de délimiter des chaînes de caractère (String) en JS
On utilise des backticks au lieu des guillemets simples et doubles
"Hello World!" // Guillemets doubles
'Hello World!' // Guillemets simples
`Hello World!` // Backticks
(Essayez de les localiser sur votre clavier)
« Encore une nouvelle manière de faire ? Pourquoi … ? 🤨 »
Plusieurs problèmes se posent avec les chaînes de caractères classiques …
'What\'s your name?';
let name = "Elisabeth";
"My name is \"" + name + "\"!";
Peu pratique. On peut vite se perdre …
let total_HT = 19.99;
let part_TVA = total_HT * 0.20;
"Total TTC : " + total_HT + part_TVA + " €";
// Résultat: "Total TTC : 19.993.9979999999999998 €"
Concaténation ou addition ? 🤔
Du coup, on utilisait des hacks comme :
let template = "<li>" +
"<a href=\"profile/" + id + "\">" +
pseudo +
"</a>" +
"</li>";
… ou encore :
let template = "<li>\
<a href=\"profile/" + id + "\">\
" + pseudo + "\
</a>\
</li>";
(Probabilité de foirage : 99,9%)
``
Concrètement, on remplace simplement
" ou ' par `
Plus besoin d'échapper les guillemets et apostrophes
`What's your name?`;
`My name is "Elisabeth"!`;
Exit la concaténation, on évalue maintenant de vraies expressions JS
let firstname = 'Elisabeth';
let lastname = 'Smith';
`My name is "${firstname} ${lastname.toUpperCase()}"!`;
// Résultat: My name is "Elisabeth SMITH"!
Une expression dans une template string est
simplement entourée par ${ et }
L'expression est d'abord évaluée, puis convertie ensuite en chaîne de caractères
let total_HT = 19.99;
let part_TTC = total_HT * 0.20;
`Total TTC : ${total_HT + part_TTC} €`;
// Résultat: "Total TTC : 23.988 €" ✔
Plus de problèmes de typage
Du vrai multilignes :
let template = `<li>
<a href="profile/${id}">
${pseudo}
</a>
</li>`;
Beaucoup plus lisible et modulable
Un moyen plus avancé pour effectuer un traitement sur les différents morçeaux de la chaîne de caractères.
Assez peu utilisé en pratique, mais on en trouve parfois.
Un "tag" (ou une "étiquette" en Français) se place juste devant une template string
monTag`My name is ${name} and i'm ${age} !`;
Un tag est une fonction qui reçoit en arguments :
monTag`My name is ${name} and i'm ${age} !`;
function monTag(stringsArray, name, age) {
// stringsArray = ["My name is ", " and i'm ", " !"];
}
let pseudo = "Hacker";
let message = "Tentative d'injection <script>alert('☠');</script>";
// ❌ CHAÎNE NORMALE
console.log( `${pseudo} dit: ${message}` );
/* Hacker dit: Tentative d'injection <script>alert('☠');</script> */
// ✔ CHAINE TAGGUÉE
console.log( protect`${pseudo} dit : ${message}` );
/* Hacker dit: Tentative d'injection :
<script>alert('☠');</script> */
function protect(strings, ...vars) {
return strings.reduce((sentence, str, index) =>
sentence + str + sanitize(vars[index] || ''), '');
function sanitize(value) {
return value.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Littéraux_gabarits#Gabarits_étiquetés
Les templates strings sont utilisées pour les besoins pratiques cités.
Pour des chaînes simples,
on continue d'utiliser ' ou "
Lisez la documentation (en Français)
(ES2015)
Les fonctions fléchées (aussi appelées des
lambdas) sont des raccourcis pour écrire des
function en JS
JavaScript étant un langage encourageant une approche fonctionnelle, les fonctions et expressions de fonctions sont très fréquemment utilisées.
Problème :
La syntaxe d'une fonction JS est assez lourde.
peopleArray.filter(function(person) {
return person !== null;
}).map(function(person) {
return person.split(' ')[1];
}).map(function(person) {
return person[0].toUpperCase() + person.slice(1);
});
Ici, beaucoup de function et de
return gênent la lecture
Pour pâlier à ce problème, une notation raccourcie a été standardisée : la notation fléchée
() => {}
À gauche, les arguments. À droite, le corps de la fonction.
Fonctions classiques
const double = function (nb) {
return nb * 2;
};
const pow = function (nb, exp) {
return Math.pow(nb, exp);
};
const capitalize = function (text) {
let letter1 = text[0];
let tail = text.slice(1);
return letter1.toUpperCase() + tail;
};
=> équivalent fléché
const double = (nb) => nb * 2;
// La flèche implique ici le "return"
const pow = (nb, exp) => Math.pow(nb, exp);
// Parenthèses obligatoires si + d'un argument
const capitalize = (text) => {
const letter1 = text[0];
const tail = text.slice(1);
return letter1.toUpperCase() + tail;
}; // "return" obligatoire s'il y a { et }
Pour renvoyer des objets littéraux, il faut utiliser des parenthèses pour ne pas confondre les accolades de l'objet avec celles du corps de la fonction.
Fonctions classiques
var getUser = function () {
return { name: 'JM', age: 31 };
}
=> équivalent fléché
let getUser = () => ({ name: 'JM', age: 31 });
(Ce pattern est beaucoup utilisé dans React)
Grâce à cette notation simplifiée, il est beaucoup plus aisé de lire un code fonctionnel :
peopleArray
.filter(person => person !== null)
.map(person => person.split(' ')[1])
.map(person => person[0].toUpperCase() + person.slice(1));
C'est tout pour la syntaxe, mais les fonctions fléchées ont aussi quelques subtilités en plus par rapport aux fonctions classiques …
Contrairement aux fonctions classiques :
// ❌ SyntaxError: Malformed arrow function parameter list
const add = add() => {
…
}
arguments n'est plus accessible
const add = () => {
// ❌ ReferenceError: arguments is not defined
const numbers = Array.prototype.slice.apply(arguments);
return numbers.reduce((total, n) => total + n);
}
this dansfunction classiques ...
Chaque function JavaScript dispose
du mot-clé this, ayant une valeur différente
suivant la façon dont la fonction est appelée ...
Dans les appels de fonctions en mode strict, this vaut undefined,
sinon, il référence l'objet global (Window, ou global dans Node.js)
(function() {
'use strict'; // Active le mode strict
function logThis() {
console.log(this);
}
logThis(); // Renvoie undefined
})();
(function() {
// Mode standard
function logThis() {
console.log(this);
}
logThis(); // Renvoie l'objet global
})();
Dans le cas d'un gestionnaire d'événements, this référence l'élément qui a déclanché l'événement
const button = document.querySelector('#myButton');
button.onclick = function () {
console.log(this); // Référence l'élément HTML `#myButton`
};
Si la fonction est appelée en tant que
méthode d'objet, this référencera cet objet
const person = {
name : 'William',
greet : function() {
console.log(this);
}
};
person.greet(); // Affichera l'objet `person`
Dans le cas d'un constructeur, la valeur de this sera définie à un nouvel objet
function Character(name, health) {
this.name = name;
this.health = health;
}
// Invocation de Character comme un constructeur (mot-clé `new`)
const player = new Character('Gandalf', 100);
this dansContrairement aux fonctions classiques, les fonctions fléchées elles
ne définissent pas de this !
(cf. documentation)
Si un this est présent dans une fonction fléchée,
il aura alors la valeur du contexte parent et non de la fonction elle-même.
Cela permet de régler plus facilement des problèmes lorsqu'on utilise un style de programmation orientée objet combiné à des appels de fonctions asynchrone.
Par exemple ici, le this dans la fonction heal pose problème ... (pourquoi d'après-vous ?)
function Character (name, health) {
// Le constructeur Character définit un nouveau `this`
this.name = name;
this.health = health;
setInterval(function heal () {
/* heal étant appelée par setInterval, `this` référence ici
l'objet global et non le constructeur. */
this.health++; // ❌ Ceci ne va pas fonctionner
}, 1000);
}
const player = new Character('Gandalf', 100);
... problème que l'on peut résoudre grâce à une simple fonction fléchée :
function Character (name, health) {
// Le constructeur Character définit un nouveau `this`
this.name = name;
this.health = health;
setInterval(() => {
/* La fonction fléchée n'ayant pas de this, ce dernier
référence bien le constructeur parent Character */
this.health++; // ✔ Ceci est correct
}, 1000);
}
const player = new Character('Gandalf', 100);
Les fonctions fléchées résolvent des problèmes, mais peuvent aussi en créer ...
listTasks en fonction fléchée :
const TodoList = {
tasks : [
{ title : 'Learn HTML/CSS', isDone : true },
{ title : 'Learn ESNext', isDone : false },
],
listTasks : function () {
this.tasks.forEach((task) => {
let marker = (task.isDone) ? '[*]' : '[ ]';
console.log(`${marker} ${task.title}`);
});
}
};
TodoList.listTasks(); // ✔ Affiche la liste des tâches
Les fonctions fléchées résolvent des problèmes, mais peuvent aussi en créer ...
this, l'appel final ne référencera plus l'objet TodoList !
const TodoList = {
tasks : [
{ title : 'Learn HTML/CSS', isDone : true },
{ title : 'Learn ESNext', isDone : false },
],
listTasks : () => {
this.tasks.forEach((task) => {
let marker = (task.isDone) ? '[*]' : '[ ]';
console.log(`${marker} ${task.title}`);
});
}
};
TodoList.listTasks(); // ❌ Ceci ne va plus fonctionner
En ES2015, on peut factoriser une méthode d'objet sans avoir besoin d'une fonction fléchée
const TodoList = {
tasks : [
{ title : 'Learn HTML/CSS', isDone : true },
{ title : 'Learn ESNext', isDone : false },
],
listTasks() {
this.tasks.forEach((task) => {
let marker = (task.isDone) ? '[*]' : '[ ]';
console.log(`${marker} ${task.title}`);
});
}
};
TodoList.listTasks(); // ✔ Fonctionne à nouveau
L'utilisation des fonctions fléchées avec jQuery n'est pas non plus conseillée. (On recommande de conserver les function classiques)
Toute la force de jQuery se fait via le binding du this dans les function classiques
$('a[target="_blank"]').click(function() {
statsCounter++;
let href = $(this).attr('href');
window.open(href, '_blank');
});
$('button').each(function() {
$(this).click();
});
L'utilisation de fonctions fléchées n'apporte aucun bénéfice réel, et "casse" le mécanisme de binding de jQuery
$('a[target="_blank"]').click(() => {
statsCounter++;
let href = $(this).attr('href'); ❌
window.open(href, '_blank');
});
$('button').each(() => {
$(this).click(); ❌
});
Les fonctions fléchées sont très fréquemment utilisées en JS moderne pour :
⚠ Elle ne remplacent pas les fonctions classiques
qui conservent elles aussi leurs avantages (nommables, constructibles, méthodes d'objets)
Si on fait du jQuery, il est recommandé de continuer d'utiliser les fonctions classiques
Lisez la documentation (en Français)
(ES2015)
La décomposition permet d'étendre un objet dit itérable en lieu et place :
Un itérable est un objet implémentant le
protocole itérable, qui permet aux objets de définir la façon
dont leurs valeurs seront parcourues.
Les Array par exemple sont des itérables, car ils implémentent nativement ce protocole.
Pour décomposer un itérable en JavaScript,
on utilise l'opérateur suivant :
...Il arrive parfois que l'on ait besoin de rassembler plusieurs tableaux en un seul.
Pour cela, on utilisait la méthode Array.prototype.concat :
let frontEnd = ['HTML', 'CSS', 'JS'];
let backEnd = ['PHP', 'Python', 'NodeJS'];
// Avant ES2015
let languages = frontEnd.concat(backEnd);
// ['HTML', 'CSS', 'JS', 'PHP', 'Python', 'NodeJS']
L'opérateur de décomposition rend la tâche plus simple grâce à sa notation intuitive :
let frontEnd = ['HTML', 'CSS', 'JS'];
let backEnd = ['PHP', 'Python', 'NodeJS'];
let languages = [...frontEnd, ...backEnd]; ✔
L'opérateur veut littéralement dire: "Décomposer à cet endroit les valeurs du tableau"
La notation très flexible permet même de mixer plusieurs valeurs :
let frontEnd = ['HTML', 'CSS', 'JS'];
let backEnd = ['PHP', 'Python', 'NodeJS'];
let languages = [...frontEnd, 'React', ...backEnd, 'Ruby'];
// ['HTML', 'CSS', 'JS', 'React', 'PHP', 'Python', 'NodeJS', 'Ruby']
On peut même se servir de cette notation pour faire une copie (et non une référence) d'un tableau:
let names = ['Paul', 'Pierrick', 'Fabian'];
// Création d'une copie du tableau "names"
let namesBis = [...names];
Tout objet itérable en JS peut être décomposé.
C'est le cas des NodeList
(renvoyés par querySelectorAll par exemple)
// Retourne une 'NodeList'
let listItems = document.querySelectorAll('ul > li');
listItems.map(li => console.log(li));
// ❌ Ne fonctionne pas car 'map' est une méthode d'Array !
// Décompose la 'NodeList' (qui est itérable) dans un 'Array'
let listItems = [ ...document.querySelectorAll('ul > li') ];
listItems.map(li => console.log(li));
// ✔ Fonctionne correctement !
Tout comme les tableaux, on peut décomposer de nouvelles valeurs dans des objets JS {}
C'est assez pratique pour créer de nouveaux objets sur la base d'anciens
let profile = { firstname: 'John', isPremium: false };
let updated = { lastname: 'DOE', isPremium: true };
let profileMerged = { ...profile, ...updated };
// { firstname: 'John', isPremium: true, lastname: 'DOE' }
On peut aussi créer facilement de
nouvelles copies d'objets
let original = { firstname: 'django', lastname: 'fett' };
let copy = { ...original, firstname: 'bobba' };
// { firstname: 'bobba', lastname: 'fett' };
Cette notation est très utilisée avec React pour créer un nouveau state sur la base d'un ancien :
setState(previousState => ({ ...previousState, ...newValues }));
⚠ Attention toutefois à la copie des sous-objets !
L'opérateur ... produit une shallow copy
et non une deep copy
let user = {
firstname: 'John',
lastname: 'DOE',
address: {
street: 'Laynard Avenue',
number: 139,
city: 'New York'
}
};
let shallowCopy = { ...user };
L'objet de l'adresse sera copié en tant que référence dans shallowCopy
ℹ Si on souhaite obtenir une vraie deep copy, il faudra alors se tourner vers
une bibliothèque utilitaire, comme Lodash qui propose une méthode _.cloneDeep()
Une autre utilité de l'opérateur ... est de décomposer un itérable pour passer ses valeurs comme arguments d'une fonction
Par exemple, la fonction JS native Math.max() permet de trouver la valeur la plus haute dans une série d'arguments :
let maxValue = Math.max(12, 7, 9, 11, 28, 6, 17); // 28
Sauf qu'en situation réelle, on a souvent notre liste de nombres sous forme d'Array
let numbers = [12, 7, 9, 11, 28, 6, 17];
let maxValue = Math.max(numbers); // ❌ NaN
La méthode max n'accepte pas les tableaux en argument 😟
Avec l'opérateur de décomposition,
le problème est vite réglé :
let numbers = [12, 7, 9, 11, 28, 6, 17];
let maxValue = Math.max(...numbers); // ✔ 28
Ici, on décompose les valeurs de numbers en tant qu'arguments de Math.max
Autre exemple avec le constructeur Date en JS
qui accepte 3 arguments :
let dateFields = [1970, 0, 1]; // 1 Jan 1970
let d = new Date(...dateFields);
/* Revient à écrire :
let d = new Date(1970, 0, 1);
*/
L'opérateur de décomposition ... permet de décomposer un itérable dans :
Il permet aussi de passer des valeurs d'un tableau sous la forme de plusieurs arguments à une fonction.
Lisez la documentation (en Français)
(ES2015)
L'affectation par décomposition est une syntaxe permettant d'extraire les données d'un tableau ou d'un objet.
La forme de la syntaxe ressemble à la structure du tableau ou de l'objet
[]Il arrive souvent de se retrouver avec un tableau contenant un ensemble de données que l'on souhaite extraire pour des traitements divers.
C'est le cas lorsqu'on traite des dates par exemple :
let dateString = '04/11/2020';
let date = dateString.split('/'); // ['04', '11', '2020']
// Extraction des éléments de la date dans des variables
let day = date[0];
let month = date[1];
let year = date[2];
Mais parfois, le tableau est plus dense :
let fullDateString = new Date().toLocaleString();
// "04/11/2020 à 16:38:02"
let date = fullDateString.split(/\s|\/|:/);
// ["04", "11", "2020", "à", "16", "38", "02"]
let day = date[0];
let month = date[1];
let year = date[2];
let hours = date[4];
let minutes = date[5];
let seconds = date[6];
En plus de risquer de faire des erreurs avec les indices,
ce code est peu élégant et difficilement lisible !
Le principe du destructuring est d'extraire des données de tableau ou d'objet dans des variables, via une syntaxe qui ressemble à la structure du tableau ou de l'objet.
Pour extraire d'un tableau, on utilise les crochets :
let [ … ] = array;
Pour extraire d'un objet, on utilise les accolades :
let { … } = object;
L'objectif de cette syntaxe est d'obtenir une lecture intuitive, et limiter les erreurs lors de l'extraction.
let fullDateString = new Date().toLocaleString();
// "04/11/2020 à 16:38:02"
let date = fullDateString.split(/\s|\/|:/);
// ["04", "11", "2020", "à", "16", "38", "02"]
let [day, month, year,, hours, minutes, seconds] = date;
L'extraction et l'assignation se font en même temps 💪
(Notez la double virgule après year, qui indique qu'on ignore la valeur intermédiaire)
La syntaxe se veut plus élégante et facile à comprendre dans de nombreuses situations :
let dateString = '04/11/2020';
let date = dateString.split('/'); // ['04', '11', '2020']
let [day, month, year] = date;
… ou plus direct :
let fullname = 'Anakin SKYWALKER';
let [firstname, lastname] = fullname.split(' ');
On peut aussi utiliser cette syntaxe pour permuter des variables :
let from = 'Français';
let to = 'English';
function swapLanguages() {
[from, to] = [to, from];
};
swapLanguages();
console.log(from); // Affiche : 'English'
console.log(to); // Affiche : 'Français'
La syntaxe autorise même d'ignorer des valeurs intermédiaires ou de départ :
let alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
let [,,,d, e,, g] = alphabet;
On peut littéralement imaginer des "trous" pour les valeurs qu'il ne
nous intéresse pas de récupérer.
L'opérateur ... est aussi appelé opérateur de rest lorsqu'il est utilisé pour de l'affectation par décomposition.
Il permet comme son nom l'indique de récupérer le reste des éléments n'ayant pas été explicitement affectés
Exemple de rest avec un Array :
let alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
let [a, b, c, ...otherLetters] = alphabet;
console.log(a); // "a"
console.log(b); // "b"
console.log(c); // "c"
console.log(otherLetters); // ['d', 'e', 'f', 'g', 'h', 'i', 'j']
{}De la même façon que pour les tableaux, il est possible de facilement extraire des propriétés d'objets
const character = {
nickname : 'Neo',
birth : new Date('2001-11-01')
};
let nickname = character.nickname;
let birth = character.birth;
const character = {
nickname : 'Neo',
birth : new Date('2001-11-01')
};
let { birth, nickname } = character;
À noter que l'ordre n'a aucune importance
Il est possible d'extraire les propriétés sous un autre nom si besoin :
const character = {
nickname : 'Neo',
birth : new Date('2001-11-01')
};
let surnom = character.nickname;
let naissance = character.birth;
const character = {
nickname : 'Neo',
birth : new Date('2001-11-01')
};
let {
nickname : surnom,
birth : naissance
} = character;
On saute des lignes pour la lisibilité
On peut aussi décomposer en profondeur :
const character = {
name : {
title : 'Mr.',
first : 'Thomas',
last : 'ANDERSON'
},
nickname : 'Neo'
};
let title = character.name.title;
let firstname = character.name.first;
let lastname = character.name.last;
let nickname = character.nickname;
const character = {
name : {
title : 'Mr.',
first : 'Thomas',
last : 'ANDERSON'
},
nickname : 'Neo'
};
let {
name : {
title,
first : firstname,
last : lastname,
},
nickname,
} = character;
À noter que l'on peu aussi utiliser l'opérateur rest avec un Object
let character = {
nickname: 'Neo',
firstname: 'Thomas',
lastname: 'ANDERSON'
};
let { nickname, ...names } = character;
console.log(nickname); // "Neo"
console.log(names); // { firstname: 'Thomas', lastname: 'ANDERSON' }
Nous venons de voir que la syntaxe de destructuring est pratique pour assigner des valeurs à des variables.
Mais elle est aussi très utile pour décomposer les arguments de fonctions qui sont des Array ou Object.
function afficherDate(tbl) {
let y = tbl[0];
let m = tbl[1];
let d = tbl[2];
let date = new Date(y, m, d);
console.log(date.toLocaleString());
}
afficherDate([2020, 11, 04]);
afficherDate([2020, 11, 07]);
afficherDate([2020, 11, 11]);
function afficherDate([y, m, d]) {
let date = new Date(y, m, d);
console.log(date.toLocaleString());
}
afficherDate([2020, 11, 04]);
afficherDate([2020, 11, 07]);
afficherDate([2020, 11, 11]);
function show(article) {
console.log(
`<article>
<h1>${article.title}</h1>
<p>${article.content}</p>
<time>${article.postedAt}</time>
</article>`);
}
let article = {
title : 'Édition spéciale !',
content : 'Lorem ipsum …',
postedAt : Date.now() };
show(article);
function show({title, content, postedAt}) {
console.log(
`<article>
<h1>${title}</h1>
<p>${content}</p>
<time>${postedAt}</time>
</article>`);
}
let article = {
title : 'Édition spéciale !',
content : 'Lorem ipsum …',
postedAt : Date.now() };
show(article);
La décomposition d'objet en argument se fait aussi beaucoup dans des librairies telles que React :
<Post title="Hello!" description="Lorem ipsum …" author="John" />
// Décomposition de l'objet `props` fourni par React
function Post ({ title, description, author }) {
description = `${description.slice(0, 100)} …`;
return (
<>
<h1>{title}</h1>
<p>{description}</p>
<aside>Written by: {author}</aside>
</>
);
}
L'affectation par décomposition est appréciée pour sa syntaxe élégante et intuitive
Elle est très utilisée en JS moderne ainsi que dans de nombreux frameworks.
Lisez la documentation (en Français)
(ES2015)
La spécification ES2015 a également introduit quelques nouveautés dans
les notations d'objets littéraux ...
let firstname = 'DOE', lastname = 'John';
var person = {
firstname: firstname,
lastname: lastname
};
// ES2015+
let person = { firstname, lastname }; ✔
// Avant ES2015
var person = {
greet: function(name, message) {
return "" + name + " : " + message;
}
};
function,
// ES2015+
let person = {
greet(name, message) { ✔
return `${name} : ${message}`;
}
};
// Avant ES2015
var KEY_PREFIX = 'myApp_';
var obj = {}; // Création de l'objet
obj[KEY_PREFIX + 'name'] = 'App name';
obj[KEY_PREFIX + 'url'] = 'http://app-custom-url.com';
// ES2015+
const KEY_PREFIX = 'myApp_';
const obj = {
[KEY_PREFIX + 'name'] : 'App name' ✔
[KEY_PREFIX + 'url'] : 'http://app-custom-url.com' ✔
};
Lisez la documentation (en Français)
(map, filter, reduce, every, some, find, findIndex)
La méthode array.map permet d'appliquer une fonction sur chaque élément du tableau
Elle renvoie un nouveau tableau contenant les valeurs modifiées
const names = ['lucas', 'romain', 'andré', 'arthur', 'lucie', 'anne'];
function capitalize(val) {
return val[0].toUpperCase() + val.slice(1);
}
const namesUpper = names.map(capitalize);
// ['Lucas', 'Romain', 'André', 'Arthur', 'Lucie', 'Anne']
La méthode array.filter permet de retirer des éléments d'un tableau grâce à une fonction de filtrage
Elle renvoie un nouveau tableau contenant les valeurs conservées
const notes = [15, 10, 8, 16, 14.5, 9, 19];
function isAverage(val) {
return (val >= 10);
}
const goodNodes = notes.filter(isAverage);
// [15, 10, 16, 14.5, 19]
La fonction de filtrage doit toujours renvoyer true ou false
Elle est appliquée sur chaque élément du tableau afin de tester la valeur
La méthode array.reduce permet de réduire un tableau en une valeur unique
Elle prend en argument une fonction qui traite chaque valeur de gauche à droite
const notes = [15, 10, 20];
function makeSum(total, val) {
return total + val;
}
const total = notes.reduce(makeSum); // 45
const average = total / notes.length; // 15
La fonction makeSum est appliquée sur chaque élément, de gauche à droite
À chaque itération, la valeur accumulée (ici total) correspond au résultat du dernier appel de la fonction
La valeur initiale correspond à la première valeur du tableau
La méthode array.every permet de vérifier si tous les éléments d'un tableau vérifient une condition
Elle renvoie un Booléen true ou false
const allNotes = [15, 10, 20, 9, 4, 18];
const goodNotes = [15, 10, 20];
function isAverage(val) {
return val >= 10;
}
allNotes.every(isAverage); // false
goodNotes.every(isAverage); // true
La méthode array.some permet de vérifier si au moins un des éléments d'un tableau vérifient une condition
Comme "every", elle renvoie un Booléen true ou false
const allNotes = [15, 10, 20, 9, 4, 18];
const goodNotes = [15, 10, 20];
const badNotes = [4, 8, 3, 5, 9];
function containsAverage(val) {
return val >= 10;
}
allNotes.some(containsAverage); // true
goodNotes.some(containsAverage); // true
badNotes.some(containsAverage); // false
La méthode array.find permet de trouver la première valeur d'un tableau qui vérifie la fonction de recherche
Elle renvoie la valeur correspondante à la condition définie dans la fonction de recherche
Si la valeur n'est pas trouvée, elle renvoie undefined
const users = [ {name: 'lucas'}, {name: 'romain'}, {name: 'phil'} ];
function findUser(name) {
return user => (name === user.name);
}
const findLucas = findUser('lucas');
const findRomain = findUser('romain');
const findTom = findUser('tom');
users.find(findLucas); // {name: 'lucas'}
users.find(findRomain); // {name: 'romain'}
users.find(findTom); // undefined
La méthode array.findIndex permet de trouver l'indice de la première valeur d'un tableau qui vérifie la fonction de recherche
Si la valeur n'est pas trouvée, elle renvoie -1
const users = [ {name: 'lucas'}, {name: 'romain'}, {name: 'phil'} ];
function findUser(name) {
return user => (name === user.name);
}
const findLucas = findUser('lucas');
const findRomain = findUser('romain');
const findTom = findUser('tom');
users.findIndex(findLucas); // 0
users.findIndex(findRomain); // 1
users.findIndex(findTom); // -1
Lisez la documentation (en Français)
(ES2015)
Les classes sont une nouvelle syntaxe inspirée des langages orientés objet, et qui permettent de manipuler plus facilement l'héritage en JS
Il a toujours été possible de faire de l'orienté objet en JavaScript, en utilisant les fonctions en tant que constructeur
Exemple :
// Déclaration d'une fonction qui sera un constructeur
function Personnage (nom) {
this.nom = nom;
}
// Écriture d'une méthode pour le constructeur "Personnage"
Personnage.prototype.parler = function parler(message) {
console.log(this.nom + ' dit: "' + this.message + '"');
};
// Instanciation du constructeur
var p1 = new Personnage('Gollum');
// Invocation de la méthode "parler"
p1.parler('Mon précieux!'); // Gollum dit: "Mon précieux!"
Cette notation déjà assez verbeuse devenait encore plus complexe lorsqu'on avait besoin de gérer de l'héritage
Exemple :
Si on voulait créer un constructeur Magicien qui hériterait
des propriétés d'un Personnage :
function Magicien (nom, couleur) {
/* Invoque le constructeur du parent (Personnage) afin de lui passer la valeur
de la propriété "nom" */
Personnage.call(this, nom);
this.couleur = couleur;
}
/* Copie du prototype du parent (Personnage) dans le prototype de l'enfant (Magicien) */
Magicien.prototype = Object.create(Personnage.prototype);
Magicien.prototype.constructor = Magicien;
// Écriture d'une méthode pour "Magicien"
Magicien.prototype.sortilege = function sortilege(formule) {
console.log(`${this.nom} ${this.couleur} invoque: "${formule}"`);
};
L'écriture d'un contructeur hérité devient plutôt fastidieuse …
JavaScript étant un langage orienté objet par prototype, il fallait sans cesse manipuler manuellement ce prototype pour gérer l'héritage correctement.
L'arrivée des classes dans la spec ES2015 a permis de simplifier cette écriture pour faire de l'objet.
Il s'agit simplement de sucre syntaxique
(le principe d'Orienté Objet par Prototype reste le même sous le capôt)
// Déclaration d'une classe "Personnage"
class Personnage {
constructor(nom) {
this.nom = nom;
this.parler('Hello!'); // On peut appeler les méthodes en interne
}
// Déclaration d'une méthode "parler"
parler(message) {
console.log(`${this.nom} dit: "${message}"`);
}
}
// Instanciation
let p1 = new Personnage('Gollum'); // Gollum dit: "Hello!"
p1.parler('Mon précieux!'); // Gollum dit: "Mon précieux!"
Cette notation avec les mots-clé class et constructor, et qui s'affranchit de function est beaucoup plus facile à lire et à écrire.
Faire de l'héritage devient beaucoup plus facile grâce à extends et super
class Magicien extends Personnage {
constructor(nom, couleur) {
super(nom); // Invocation du constructeur parent (Personnage)
this.couleur = couleur;
}
sortilege(formule) {
console.log(`${this.nom} ${this.couleur} invoque: "${formule}"`);
}
}
Des éléments de classe dits statiques définissent des éléments qui appartiennent à la classe et non aux instances.
On les utilise généralement comme des propriétés ou méthodes utilitaires.
Exemple : Ici, la méthode statique distance est une méthode utilitaire permettant
d'obtenir une distance entre deux points.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
let pA = new Point(5, 0);
let pB = new Point(8, 0);
Point.distance(pA, pB); // Affiche: 3
Elle appartient intrinsèquement à la classe Point (et non aux instances pA et pB).
Autre exemple avec une propriété statique Count qui pourrait permettre
de compter le nombre d'instances de la classe
class Point {
static Count = 0;
constructor(x, y) {
Point.Count++;
this.x = x;
this.y = y;
}
}
let pA = new Point(5, 0);
let pB = new Point(8, 0);
console.log(Point.Count); // 2 (car Point a été instanciée 2 fois)
Les getters et setters sont des fonctionnalités disponibles sur les objets JS, et depuis ES2015 sur les classes JS.
Ils permettent de manipuler l'obtention et la définition de valeurs à des propriétés d'instance
Ex: Création d'une propriété dynamique fullname
class Character {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
get fullname() {
return `${this.firstname} ${this.lastname.toUpperCase()}`;
}
}
let hobbit = new Character('Frodo', 'Baggins');
// Invoque le getter
console.log( hobbit.fullname ); // Affiche : "Frodo BAGGINS"
Lorsque la propriété fullname est assignée
class Character {
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
set fullname(value) {
[this.firstname, this.lastname] = value.split(' ');
}
}
let hobbit = new Character('Frodo', 'Baggins');
hobbit.fullname = 'Sam Gamgee'; // Invoque le setter
console.log( hobbit.firstname ); // Affiche : "Sam"
console.log( hobbit.lastname ); // Affiche : "Gamgee"
Stage 4 : Arrive enfin dans ES2022 💪
Class field declarations for JavaScript
(Déjà accessible depuis longtemps via des transpilers comme Babel)
Parfois, on a besoin d'utiliser le constructeur pour définir des valeurs par défaut :
class Square {
constructor() {
this.x = 0;
this.y = 0;
this.width = 200;
this.height = 100;
}
moveCoordinates(x, y) {
this.x = x;
this.y = y;
}
}
Avec la nouvelle notation, il est possible de placer les valeurs directement dans le corps de la classe
(et éviter ainsi de polluer le constructeur)
class Square {
x = 0;
y = 0;
width = 200;
height = 100;
moveCoordinates(x, y) {
this.x = x;
this.y = y;
}
}
Un champs privé désigne une propriété de classe à laquelle il n'est possible d'accéder que depuis l'intérieur de la classe (et non depuis les instances)
Jusqu'à présent, toutes les propriétés de classes que vous avez vues étaient publiques …
Exemple avec une propriété publique title :
class App {
title = 'My super application'; // Propriété publique
}
const myApp = new App();
/* Accès à la propriété publique en lecture/écriture,
depuis l'extérieur de la classe */
myApp.title; // 'My super application'; ✔
myApp.title = 'My FANTASTIC application'; ✔
myApp.title; // 'My FANTASTIC application'; ✔
Pour créer un champs privé, il convient de le marquer par un # :
class App {
title = 'My super application';
#secret_token = 'oavMtUWDBTM'; // Propriété privée !
backend_call() {
// ✔ Accès au champs privé en interne
return fetch(`/api/?token=${this.#secret_token}`);
}
}
const myApp = new App();
// ❌ Accès impossible au champs privé en externe
myApp.#secret_token; /* ❌ SyntaxError: Private field '#secret_token' must be
declared in an enclosing class */
myApp.backend_call(); // ✔ Fonctionne correctement
Les classes permettent de faciliter le style de programmation orienté objet en JavaScript
Leur syntaxe simple est très appréciée dans les frameworks JS comme Angular ou ReactNative
(D'excellentes et très complètes ressources par Delicious Insights)
Lisez la documentation (en Français)
(ES2015)
Les modules ES2015 sont un nouveau moyen d'organiser et structurer les programmes JavaScript
Au début, les programmes JavaScript étaient assez petits et réalisaient des tâches isolées et plutôt simplistes.
Aujourd'hui, les programmes sont plus avancés et plus volumineux, nécessitant une meilleure découpe et une meilleure organisation des fonctionnalités.
<script>Jusqu'à présent, on chargeait les fichiers JS directement via la balise <script>
<script src="math.js"></script>
<script src="database.js"></script>
<script src="storage.js"></script>
Il y a 2 principaux problèmes avec cette approche :
<script src="database.js"></script>
<script src="storage.js"></script>
// database.js
const save = (data) => {
/* envoie une donnée au serveur
pour sauvegarde ... */
}
// storage.js
const save = (itemName, data) => {
return localStorage.setItem(
itemName,
JSON.stringify(data)
);
}
Les constantes save provoquent un conflit dans l'espace global de la page HTML.
L'idée est de fournir un mécanisme pour diviser les programmes JavaScript en plusieurs modules qu'on pourrait importer les uns dans les autres, en résolvant le problème d'encapsulation.
ℹ
Ce mécanisme est majoritairement disponible dans les environnement de dev moderne, et ce grâce aux transpilers (babel) et aux bundlers
(webpack, parcel, rollup)
Les environnements de développement pour React, Vue, Angular, ReactNative, ...etc sont généralement déjà pré-configurés pour prendre en charges les modules.
Node.js également prend en charge nativement les modules ES2015 depuis la v13
Pour plus d'informations sur l'utilisation des modules dans Node.js, allez lire la doc.
Plus récemment, les navigateur web ont aussi commencé à prendre en charge ces fonctionnalités nativement.
Pour plus d'informations, allez lire l'excellent article (en Français) sur le MDN :
Les modules JavaScript
ℹ
Ce cours porte sur les modules tels que définis par le standard ES2015
Ne seront pas abordés les différentes particularités des environnements pré-cités (pour cela, référez-vous aux liens donnés plus haut)
Un module est un fichier .js (ou .mjs) à partir duquel il est possible
d'importer et d'exporter des éléments.
La spécification introduit pour ce faire 2 mots-clé :
import: pour importer des éléments dans ce moduleexport: pour exporter des éléments vers d'autres modulesSoit le module database.js suivant :
// database.js
export const DEFAULT_COLLECTION = 'articles';
export function save(data, collectionName) {
return db.collection(collectionName).push(data);
}
Le mot-clé export marque la constante "DEFAULT_COLLECTION" et la fonction "save" comme étant des éléments exportables
Depuis un autre module, il sera possible de récupérer ces éléments avec le mot-clé "import" :
// index.js
import { save, DEFAULT_COLLECTION } from './database.js';
save(data, DEFAULT_COLLECTION)
.then(() => console.log('✔ Data saved!'));
⚠ Notez qu'on écrit ./ devant le nom de fichier
Cela n'est valable que lorsqu'on récupére des fichiers qui appartiennent à notre arborescence
Il est possible d'exporter une infinité d'éléments
(généralement, il s'agit de fonctions, d'objets ou de constantes)
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export function addAll(...items) {
return ensureNumber(items).reduce(add);
}
export function multiplyAll(...items) {
return ensureNumber(items).reduce(multiply);
}
const ensureNumber = (arr) => {
if (!arr.every(item => typeof item === 'number')) {
throw new Error(`Elements of [${arr}] must be numbers only`);
}
return arr;
}
... et les récupérer depuis un autre fichier au gré des besoins :
// index.js
import { add, addAll } from './math.js';
add(3, 7); // 10
addAll(3, 7, 5, 2); // 17
On peut aussi importer ces éléments avec un alias, grâce au mot-clé as :
// index.js
import { add, addAll as addMultiple } from './math.js';
add(3, 7); // 10
addMultiple(3, 7, 5, 2); // 17
On peut dans certains cas avoir besoin de recupérer tous les éléments exportables d'un module.
On utilise alors le marqueur * lors de l'import :
// index.js
import * as math from './math.js';
math.add(3, 7); // 10
math.addAll(3, 7, 5, 2); // 17
(dans ce cas, il est obligatoire de définir un alias avec as)
Il est possible de marquer un élément exportable de module comme étant celui par défaut.
On utilise pour cela le mot-clé default avec l'export :
// storage.js
export default const storage = {
save(itemName, data) {
return localStorage.setItem(itemName, JSON.stringify(data));
},
get(itemName) {
return JSON.parse(localStorage.getItem(itemName));
}
}
... dans ce cas, on récupére la valeur par défaut sans utiliser les accolades {}
// index.js
import storage from './storage.js';
let data = storage.get('articles');
data.push('New item');
storage.set('articles', data);
// App.jsx
import React from 'react';
import Navbar from './Navbar.jsx';
import menuItems from './menuItems.js';
export default function App() {
return (
<>
<Navbar items={menuItems} />
<div class="container">
<h1>Welcome on my App</h1>
</div>
</>
);
}
// Navbar.jsx
import React from 'react';
export default function Navbar({items}) {
return (
<nav>
<ul>
{items.map(({name, link}) => (
<li>
<a href={link}>{name}</a>
</li>
))}
</ul>
</nav>
);
}
Les modules ES2015 permettent de mieux découper et encapsuler les fichiers d'une application JavaScript
Une plongée illustrée dans les modules ECMAScript
Excellent article de Mozfr, en Français
(ES2015)
Pendant longtemps, XMLHttpRequest et jQuery ont été des solutions viables pour écrire des requêtes Ajax dans une page web …
Mais les échanges de données devenant de plus en plus fréquents et complexes, des problèmes ont vite eus lieu !
Par exemple, le code devenait très complexe et difficilement maintenable lors d'appels à la chaîne, ou en parallèle
Ici par exemple, comment s'assurer que les 2 requêtes Ajax soient finies pour lancer l'appel à "init()" ?
var settings = { method: 'GET' };
$.ajax('/getTweets', settings).done(onTweetsDone);
$.ajax('/getArticles', settings).done(onArticlesDone);
function onTweetsDone(tweets) { … }
function onArticlesDone(articles) { … }
function init() {
// On voudrait ici pouvoir récupérer les "tweets" et les "articles"
}
Très vite avec la méthode classique des "callbacks", on obtenait du code difficilement lisible et maintenable.
Cela a même donné lieu au nom de l'anti-pattern :
la "callback hell"
Pour résoudre ce problème et faciliter la gestion des traitements asynchrones, le langage JavaScript a vu arriver dans son implémentation ES2015 les Promesses
Les promesses permettent :
Une promesse en JavaScript est définie par l'objet Promise
Cet objet représentent une valeur pouvant être disponible maintenant, dans le futur, ou jamais.
Une promesse peut donc être dans 3 états différents :

Un objet "Promise" met à disposition 2 méthodes : .then() et .catch(), respectivement pour gérer une opération réussie et une opération échouée.
On peut ainsi écrire une chaîne de promesse comme suit :
// Admettons une fonction "lireFichier" qui retourne une Promise JS :
lireFichier('myFile.txt')
.then(text => console.log(`Le fichier contient: ${text}`))
.catch(err => console.error(`Erreur: ${err.message}`));
Pour faire une requête Ajax en utilisant des promesses, on fait appel en web à la fonction native fetch()
Cette fonction renvoie une promesse
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('Les données récupérées sont ', data);
})
.catch(err => console.error(err));
Étudions ce code ligne par ligne …
fetch('/api/data')
retourne une promesse dont la valeur est un objet "Response"
fetch('/api/data').then(response => response.json())
cette "Response" est transformée en JSON via la méthode ".json()", qui elle-même retourne une Promesse
…
.then(data => {
console.log('Les données récupérées sont ', data);
})
Si jusque-là, aucune erreur ne s'est produite, les données JSON sont passées à la dernière fonction de la chaîne de promesse
…
.catch(err => console.error(err));
En cas d'erreur provoquée par n'importe quelle promesse, c'est cette fonction qui la traitera
L'avantage d'utiliser les promesses est qu'il est très facile d'enchaîner les opérations asynchrones à la suite, tout en ayant une gestion d'erreur facilitée
On peut même très facilement résoudre le problème de parallèlisation précédent, en utilisant Promise.all()
La méthode "Promise.all()" accepte un tableau de promesses en argument
Elle attendra que toutes les promesses soient résolues pour passer à la suite, tout en agrégant les données (et ça c'est top) !
Promise.all([
fetch('/getTweets').then(res => res.json()),
fetch('/getArticles').then(res => res.json())
])
.then(init)
.catch(err => console.error(err));
function init([tweets, articles]) {
…
}
Malgré la simplicité du concept des promesses, il n'est pas toujours simple d'écrire une chaîne de promesse sans mélanger les fonctions et les instructions.
Pour permettre d'écrire du code asynchrone encore plus facilement, deux mots-clé ont été introduits dans la spécification ES2017 de JavaScript
Il s'agit des mots-clé async et await
On les utilise comme des "marqueurs" pour définir des éléments asynchrones, mais le concept en soi ne change pas.
Ils permettent de simplifier l'écriture de code asynchrone
C'est du "sucre syntaxique" autour des Promesses en JavaScript
async function init() {
const response = await fetch('/api/data');
const data = await response.json();
console.log('Les données récupérées sont ', data);
}
init();
Exit les fonctions dans tous les sens !
On a l'impression de lire du code s'exécutant de manière synchrone, mais le mécanisme d'asynchronicité ne change pas !
Avec async/await, les erreurs sont gérées par un bloc try/catch :
async function init() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log('Les données récupérées sont ', data);
} catch (err) {
console.error(`Une erreur s'est produite `, err);
}
}
init();
Même la parallèlisation devient beaucoup plus simple à écrire :
async function init() {
const [tweets, articles] = await Promise.all([
fetch('/getTweets').then(res => res.json()),
fetch('/getArticles').then(res => res.json())
]);
// ...
}
Les promesses sont des objets spéciaux qui représentent une valeur qui pourra changer dans le futur
Elles permettent de mieux enchaîner les opérations asynchrones
Les mots-clé async et await sont du sucre syntaxique facilitant l'écriture d'une codebase asynchrone
Lisez la documentation (en Français)