ES2020 : Quoi de neuf Javascript ?

ECMAScript 2020 : quoi de neuf JavaScript ?

Une nouvelle version du standard ECMAScript sur lequel repose JavaScript est prévue cette année sous le nom de ES11 ou ES2020. Voyons voir ce que nous réserve cette nouvelle mouture.

Il était une fois…

JavaScript a été créé en 1995 par Brendan Eich. Il a été standardisé sous le nom d’ECMAScript en juin 1997 par Ecma International dans le standard ECMA-262…

Non ne vous inquiétez pas, je ne vais pas vous refaire un historique de JavaScript.

Sachez juste que depuis la version 6 du standard ECMAScript (ES6) sortie en juin 2015, la mise à jour du standard est désormais annuelle. La version 11, abrégé ES11 ou encore ES2020 est prévue logiquement pour le mois de juin de cette année.

Chaque nouvelle fonctionnalité suit un processus normalisé en 5 étapes :

  • Étape 0 (idée) : Une idée de fonctionnalité est proposée;
  • Étape 1 (proposition) : L’idée est formalisée avec généralement un aperçu de l’API et des détails d’implémentation;
  • Étape 2 (brouillon) : Une implémentation expérimentale de la fonctionnalité est fournie. À ce stade, la proposition a des chances de faire partie de la prochaine version de la spécification ;
  • Étape 3 (candidat) : La proposition de la fonctionnalité est quasiment mature. Celle-ci est en attente d’approbation par les réviseurs;
  • Étape 4 (approuvée) : La nouvelle fonctionnalité est prête à intégrer la prochaine version de la spécification;

Vous pouvez aller voir les potentielles nouvelles fonctionnalités du standard sur le dépôt Github : https://github.com/tc39/proposals

Quoi de neuf docteur ?

La nouvelle version du standard apporte son lot de nouveautés :

  • Optional chaining;
  • nullish coalescing operator;
  • BigInt;
  • Promise.allSettled;
  • String.prototype.matchAll;
  • globalThis;
  • import();
  • for-in.

Optional chaining

Pour commencer, nous allons voir une nouvelle fonctionnalité vraiment pratique qui va vous aidez à réduire votre code et améliorer sa lisibilité, il s’agit d’un nouvel opérateur nommé chaînage optionnel ou “optional chaining”.

Il arrive régulièrement de devoir vérifier l’existence d’un objet avant d’accéder à un attribut de ce dernier.

Prenons un exemple. Imaginons que nous ayons une API REST qui nous renvoie un article de blog au format JSON :

Nous stockons cette réponse dans une variable post et nous souhaitons ensuite accéder au nom d’utilisateur de l’auteur du premier commentaire :

  1. On vérifie que la variable post existe, si c’est le cas on récupère son tableau de commentaires;
  2. On vérifie que le tableau de commentaires existe, si c’est le cas on récupère le premier commentaire;
  3. On vérifie que le premier commentaire existe, si c’est le cas on récupère l’auteur;
  4. Et enfin, on vérifie que l’auteur existe et si c’est le cas on récupère son nom d’utilisateur.

Fiouf !!! La syntaxe est lourde non ? C’est quelque chose que l’on rencontre régulièrement notamment en front quand on souhaite récupérer des données provenant d’une API.

Bref, parlons de cet opérateur de chaînage optionnel, celui-ci s’écrit ?. et fonctionne de la même manière que l’opérateur de chaînage traditionnel . sauf que contrairement à celui-ci , l’opérateur ?. ne provoque pas d’erreur si l’un des éléments de la chaîne vaut null ou undefined et renvoie à la place undefined.

Voyons voir notre exemple précédent en utilisant ce nouvel opérateur :

Il n’y a pas photos c’est quand même plus propre ! On peut également l’utiliser avec un appel de fonction :

Nullish coalescing Operator

Nous avons régulièrement besoin d’attribuer une valeur par défaut à une variable si une autre variable qu’on lui affecte n’existe pas, pour cela nous utilisons l’opérateur logique ||

Prenons l’exemple de paramètres d’une API REST que nous récupérons depuis un objet config :

L’opérateur || renvoie la première expression si celle-ci est évaluée à true sinon il renvoie la seconde expression.

L’avantage de cette syntaxe est qu’elle est plus concise par rapport à l’utilisation d’une condition ternaire :

Malheureusement, cela pose certains soucis avec les “falsy values“. Les “falsy values” sont des valeurs qui sont évaluées à false quand elles sont utilisées dans un contexte booléen (comme les conditions, les opérateurs logiques, les boucles, etc.). 

En JavaScript, les “falsy values” sont :

  • false ;
  • 0;
  • "" ou '';
  • null;
  • undefined;
  • NaN.

On se rend compte que dans notre exemple,  config.debug || true renverra  true si la propriété debug de l’objet config est égale à false.

Il est possible de remédier à ce problème en utilisant une condition if/else ou une condition ternaire :

Mais ECMAScript 11, propose un nouvel opérateur nommé le “nullish coalescing operator” qui s’écrit ??. Celui-ci s’utilise de la même façon que l’opérateur || à la différence qu’il renvoie la première expression si celle-ci est différente de null ou undefined et la seconde expression dans le cas contraire.

Ce qui donne pour notre exemple :

BigInt

Nous avions jusqu’à présent sept types en JavaScript :

  • number : Les nombres, qu’il soit entiers ou décimaux stockés, au format 64 bits IEEE-754, également appelés nombres à virgule flottante double précision. La valeur des nombres entiers ne peut pas être supérieur à 253 -1 et inférieur à -253 -1 ;
  • string : Les chaînes de caractères au format UTF-16;
  • boolean : Représente les valeurs booléennes true ou false;
  • null : Ce type ne possède qu’une seule valeur : null. Il représente l’absence de valeur ou la non-existence d’une référence (bonus n°1 : pourquoi typeof null renvoie object ? );
  • undefined : ce type ne possède qu’une seule valeur : undefined. Il représente la valeur d’une variable déclarée, mais non assignée;
  • symbol : Ce type permet de créer des valeurs uniques pouvant notamment être utilisées pour représenter des identifiants de propriétés d’un objet;
  • object : Le type object permet de stocker des collections de données et créer des entités plus complexes;

Bonus n°2 : Les six premiers types sont appelés primitives, ils sont passés par valeur lors de l’appel d’une fonction contrairement au type object qui lui est passé par référence.

La nouvelle version du standard ajoute un huitième type qui est bigint.

Pourquoi utiliser BigInt ?

Comme nous l’avons vu, la valeur des nombres entiers ne peut pas être supérieure à 253 et inférieur à -253, cela s’explique par le fait que le type number est stocké, au format 64 bits IEEE-754, permettant de représenter des nombres à virgule flottante et que seulement 53 bits (+ 1 bit de signe) sont utilisés pour représenter les entiers. Je vous invite à aller lire cet article pour mieux comprendre (https://2ality.com/2012/04/number-encoding.html)

Cependant, il arrive parfois que l’on ait besoin de représenter des nombres entiers allant au-delà de ces limites. Par exemple :

  • Utiliser de grands nombres entiers comme identifiants uniques, comme c’est le cas pour Twitter qui utilise un nombre entier de 64 bits pour leur identifiant de tweets;
  • Avoir un timestamp à haute précision (à la nanoseconde par exemple);
  • Communiquer avec un autre système qui représente ses entiers en 64 bits;

Comment ça s’utilise ?

C’est très simple pour créer un bigint, il suffit d’ajouter le suffixe n à un entier ou bien en appelant la fonction BigInt et en lui passant en paramètre soit une chaîne de caractères soit un entier :

Opérateurs arithmétiques

Les opérateurs arithmétiques sont bien entendu utilisables :

Vous remarquerez que la division ne garde que la partie entière.

Il n’est pas possible par contre de mélanger des entiers de type bigint avec des entiers de type number :

Nous devons explicitement convertir les entiers de type number en bigint :

Opérateurs de comparaison

Les opérateurs de comparaisons sont quant à eux utilisables avec les entiers de type bigint et de type number :

Bien entendu l’opérateur “===” renvoie false puisque celui-ci vérifie à la fois que les deux opérandes sont égaux, mais aussi de même type.

Promise.allSettled

Avant de commencer de parler de cette nouvelle fonctionnalité, il est bon de rappeler les différents états d’une promesse :

  • En attente (pending) : L’état initial de la promesse;
  • Résolue (fulfilled) : En cas de succès du traitement asynchrone;
  • Rejetée (rejected): En cas d’erreur du traitement asynchrone;
  • Acquittée (settled) : Lorsque la promesse est terminée, c’est-à-dire soit résolue ou rejetée;

États d'une promesse

Les différents états d’une promesse

Lorsque l’on souhaite lancer plusieurs promesses en parallèle, nous disposons actuellement de deux fonctions :

  • Promise.all : Cette méthode renvoie une promesse qui est résolue quand l’ensemble des promesses ont été résolues ou qui est rejetée dès que la première des promesses acquittées est rejetée;
  • Promise.race : Cette méthode renvoie une promesse qui est résolue ou rejetée dès que la première des promesses acquittées est résolue ou rejetée.

Prenons un petit exemple :

Comment faire lorsque l’on souhaite attendre l’acquittement de l’ensemble des promesses, quels que soient leurs états (résolues ou rejetées) ?

C’est là qu’entre en jeu la méthode Promise.allSettled. Celle-ci renvoie une promesse qui est résolue une fois que l’ensemble des promesses ont été acquittées (résolues ou rejetées).

Reprenons l’exemple précédent :

Bonus n°3 : Une autre fonctionnalité, concernant les promesses, est en cours de validation (étape 3), il s’agit de la méthode Promise.any qui renvoie une promesse résolue dès l’acquittement d’une des promesses. 

String.prototype.matchAll

On va s’attaquer à un sujet que tous les développeurs aiment et maîtrisent parfaitement : les expressions régulières (comment ça je troll ?).

Entrons dans le vif du sujet et voyons comment extraire les couples clé/valeur, séparés par le caractère =, dans la chaîne de caractères suivante :

Nous allons pour cela utiliser une expression régulière, comme nous sommes tous des développeurs ayant fait regex LV2, nous écrivons celle-ci sans aucun problème :

Pour les autres, détaillons notre expression régulière :

  • ([^=|\s]+) : Le premier groupe capturant permettant d’extraire la clé, celui-ci est composé de n’importe quel caractère à l’exception du caractère = et des espaces blancs ( espace, tabulation, saut de ligne ou saut de page);
  • = : Le caractère séparant la clé de sa valeur;
  • ([^\s]+) : Le second groupe capturant permettant d’extraire cette fois-ci la valeur, celui-ci est composé de n’importe quel caractère à l’exception des espaces blancs ( espace, tabulation, saut de ligne ou saut de page).

Le flag “g” à la fin de l’expression régulière indique que l’on souhaite rechercher toutes les correspondances possibles.

Voyons à présent ce que nous propose la version actuelle du standard ECMAScript, pour extraire la clé et la valeur.

La méthode String.prototype.match

La méthode String.prototype.match permet d’obtenir un tableau contenant l’ensemble des correspondances entre une chaîne de caractères et une expression régulière.

Appliquons cette méthode à notre exemple :

Le résultat que cette méthode n’est pas vraiment celui que l’on attendait. Cela s’explique par la présence du flag “g” qui change le résultat de cette méthode. S’il est présent, toutes les correspondances sont renvoyées, mais sans leurs groupes capturants.

La méthode RegExp.prototype.exec

On a vu que précédemment que la méthode String.prototype.match ne fonctionnait pas comme nous le souhaitions avec le flag “g” pour extraire les groupes capturants.

Une solution est de faire appel à la méthode RegExp.prototype.exec. Cette méthode retourne une correspondance entre une expression régulière et une chaîne de caractères.

Si l’on utilise cette méthode avec le flag “g“, celle-ci garde en mémoire l’index à partir duquel chercher la prochaine correspondance. Si aucune correspondance n’est trouvée la méthode renvoie null.  Il est donc possible de boucler sur l’exécution de cette méthode afin de trouver l’ensemble des correspondances :

Cette solution n’est pas idéale, car elle pose un souci dans le cas où le flag “g” a été omis. En effet,  la méthode RegExp.prototype.exec renverra à chaque fois la première correspondance créant ainsi une boucle infinie.

La méthode String.prototype.matchAll à la rescousse

La nouvelle méthode String.prototype.matchAll va permettre de remédier aux problèmes vus précédemment. Celle-ci renvoie un itérateur contenant l’ensemble des correspondances et leurs groupes capturants entre une chaîne de caractères et une expression régulière.

Il est également possible de convertir l’itérateur en tableau via l’opérateur de décomposition (spread operator):

ou la méthode “Array.from” :

Cette méthode provoque également une erreur TypeError dans le cas où le flag “g” est manquant de l’expression régulière.

globalThis

Il arrive parfois que nous ayons besoin d’accéder au contexte global. Ce contexte global est différent suivant l’environnement d’exécution :

  • window : Dans le cas du navigateur web;
  • self : Dans le cas de l’utilisation d’un web worker;
  • global : Dans le cas de l’environnement NodeJS.

L’accès à ce contexte global peut se justifier par exemple pour la vérification de l’existence d’une fonctionnalité ou l’ajout d’une fonctionnalité via un polyfill.

Prenons comme exemple la vérification de la prise en charge du type BigInt par l’environnement d’exécution et l’utilisation d’un polyfill dans le cas où celui-ci n’est pas présent :

Cette manière de faire n’est par contre pas pérenne, si de nouveaux environnements d’exécution voient le jour nous devrons mettre à jour notre code. 

La nouvelle version du standard nous fournit une propriété standardisée globalThis permettant d’accéder au contexte global, quel que soit l’environnement.

Notre code précédent se retrouve ainsi grandement simplifié :

Le nom globalThis a été choisi en référence au fait que la valeur this peut faire référence au contexte global comme nous avons pu le voir dans un précédent article.

import()

L’importation de modules se fait via l’instruction import :

L’utilisation de module ECMAScript pour NodeJS est encore au stade expérimental.

Cette importation de modules se fait de façon statique, ce qui ne pose aucun souci dans 90% des cas. Mais il arrive que l’on ait besoin d’importer un module de façon dynamique. C’est le cas par exemple de l’utilisation d’un module pollyfill si une fonctionnalité n’est pas présente par défaut ou pour charger des fonctionnalités à la demande.

Malheureusement, il n’est pas possible d’importer un module de façon dynamique avec cette instruction, que ce soit via une variable ou une condition :

Nous devons pour cela utiliser l’importation dynamique de modules via l’instruction import(). Cette instruction charge un module de façon asynchrone et retourne une promesse qui, si elle est résolue, renvoie le module en question :

On peut également charger dynamiquement plusieurs modules en même temps via la méthode Promise.all :

Et également utiliser une fonction async

Pour accéder à l’exportation par défaut d’un module vous devez utiliser la propriété default du module chargé :

for…in

Vous allez me dire, mais la boucle for...in existe depuis un moment et vous avez entièrement raison. Mais alors c’est quoi cette nouvelle fonctionnalité ?

Ben il n’y a pas vraiment de nouvelle fonctionnalité, mais juste une mise au point au niveau du standard concernant le comportement de la boucle for...in . Jusqu’à présent, l’ordre dans lequel les éléments devaient être parcourus n’était pas défini et chaque moteur JavaScript possédait son ordre de parcours. Avec cette mise à jour, l’ordre de parcours est standardisé.

Quid de la compatibilité ?

Le nouveau standard d’ECMAScript apporte son lot de nouveautés particulièrement intéressantes. Malheureusement ses fonctionnalités ne sont pas supportées par tous les navigateurs ou par NodeJS, je vous invite à visiter le site Caniuse pour vérifier la compatibilité de chaque fonctionnalité pour les navigateurs et node.green pour NodeJs.

En route vers le futur ?

Il y a d’autres fonctionnalités intéressantes qui pourraient à l’avenir faire partie du standard. Celles-ci sont visibles sur le dépôt Github du standard : https://github.com/tc39/proposals.

Si cet article vous a intéressé, n’hésitez pas à me le dire et j’en ferais un second (plus court) sur les fonctionnalités à venir.

En attendant, allez réviser vos Regex (#troll).

 


Annonces partenaire

Je suis lead developer dans une boîte spécialisée dans l'univers du streaming/gaming, et en parallèle, je m'éclate en tant que freelance. Passionné par l'écosystème JavaScript, je suis un inconditionnel de Node.js depuis 2011. J'adore échanger sur les nouvelles tendances et partager mon expérience avec les autres développeurs. Si vous avez envie de papoter, n'hésitez pas à me retrouver sur Twitter, m'envoyer un petit email ou même laisser un commentaire.

4 commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.