ES2020 : les nouveautés dans ton Javascript !

En dehors du titre, le générique masculin est utilisé sans aucune discrimination et uniquement dans le but d'alléger le texte.

ES2020 : les nouveautés dans ton Javascript !

ES2020 vient d’être validé par l’ECMA et y’a des trucs de fifous ! C’est des nouvelles features toutes chaudes dans ton Javascript. Ça s’est fait calmement il y a quelques jours. Et aujourd’hui, je te propose un tour du propriétaire des changements les plus intéressants.



ECMA, Ecmascript, TC39 et ES2020

Alors, avant de commencer, il faut que je file un peu de contexte sur ce dont je vais te parler. Tu sais déjà tout sur le fonctionnement de tous ces termes ? T’es trop un geek, tu peux passer directement à la partie suivante.

Sinon, t’es quand même un geek, mais il te faut plus de contexte. J’ai déjà évoqué l’ECMA dans l’article sur l’incroyable popularité de Javascript. Parlons-en plus en détail.

L’ECMA est une organisation qui gère de la standardisation dans le domaine informatique. Concrètement elle va imposer des standards pour des technologies, des formats de fichiers ou des langages de programmation. Dont Javascript avec la spécification ECMAScript.

Javascript est basé sur une spécification appelée ECMAScript. Cette spécification est là pour standardiser Javascript, mais aussi d’autres langages de script. ECMAScript est géré par un groupe bien précis, à intérieur de l’ECMA, le comité appelé TC39.

TC39 est un comité dans l’ECMA qui s’occupe exclusivement de l’ECMAScript. Donc de la standardisation de Javascript. Y’a pas longtemps ils ont approuvé une nouvelle version 2020, appelée ES2020, avec plein de nouvelles features.

Quand j’ai vu ça, immédiatement, c’est plein d’entrain que j’ai lancé mon enquête.



es2020


BigInt

Dans l’ES2020 on trouve d’abord BigInt. BitInt est un nouveau type de primitive incorporé dans Javascript ! Petit rappel de toutes les primitives.

  • String (“toto”, “tata”, “titi”)
  • Numbers (-1, 1, 1.1)
  • Booleans (true, false)
  • Symbol (valeur unique)
  • Undefined (undefined)
  • Null (null)

On peut donc désormais rajouter à cette liste BigInt.

BigInt est là pour aller plus loin que la limitation de Number. En effet, à partir d’un certain chiffre en utilisant Number, ça part en sucette et ça affiche n’importe quoi. Tu peux voir le maximum que Number peut gérer avec la constante MAX_SAFE_INTEGER.



const limitSafeNumber = Number.MAX_SAFE_INTEGER;

console.log(limitSafeNumber); // 9007199254740991

// going above this limit you'll get weird stuff
console.log(limitSafeNumber + 10); // 9007199254741000
console.log(limitSafeNumber + 20); // 9007199254741012
console.log(limitSafeNumber + 30); // 9007199254741020

Pour éviter ce comportement de l’enfer, ES2020 te propose la primitive BigInt. La façon de l’utiliser est un peu bizarre. Il faut rajouter la lettre n à la fin des chiffres que tu utilises.

const bigIntNumber = 900000000000000000n;

console.log(bigIntNumber); //900000000000000000n
console.log(bigIntNumber * 20n); // 18000000000000000000n

C’est ainsi que tu peux désormais utiliser des valeurs complètement insane en Javascript. Sky is the limit, ça commence fort, accroche toi bien c’est que le début.

bigint


Dynamic Import

ES2020 accueille également les Dynamic Import. C’est une feature qui est utilisable depuis un moment dans pas mal de browser. Toujours expérimental en node. C’est fort possible que tu l’utilises déjà. Elle est désormais officiellement dans la spécification de cette année.

Pour comprendre ce que c’est cette feature, il faut qu’on s’intéresse à la façon traditionnelle d’importer un module dans notre application Javascript. On le fait de façon static, et surtout on preload le module peut importe si on en a besoin ou pas.

import { myModule } from '/path/moduleFile.js';

const superToto = false;

if (superToto) {
  myModule.doStuff();
}

Ici de façon static le module est importé dans la page. Ensuite, comme le flag superToto est négatif, la fonction du module ne sera pas utilisée. Mais on a quand même le module preloadé dans ta page.

Dynamic Import vient régler ce problème. Tu peux importer un module à la demande ! Ça te permet donc de faire du lazy-loading en esquivant le coût du chargement, du parsing et de la compilation. Tout se fera à la volée seulement si l’utilisateur en a vraiment besoin. C’est fait de façon asynchrone, donc tu peux soit gérer ça avec des promesses ou avec async/await comme ci-dessous.

const superToto = true;

if (superToto) {
  const { doStuff } = await import('/path/moduleFile.js');
  doStuff();
}


Optional Chaining

L’optional chaining fait aussi partie de l’ES2020. Et ça donne du plaisir ! Quand tu dois accéder aux propriétés imbriquées dans un objet, t’es obligé d’être super prudent. Si tu commences à accéder à une propriété sur un objet qui n’existe pas, ça te pète à la gueule immédiatement.

const superToto = {
    hero = true,
    location: {
        city: {
          name: "Lyon"
        }
    },
    power: {
        psychic: ['telekinesis']
    }
}

if(superToto.power && superToto.power.psychic) {
    console.log(superToto.power.psychic) //['telekinesis']
}

console.log(superToto.location.country.name) // throw error

L’optional chaining règle ce problème en faisait la vérification à ta place et en renvoyant undefined si la propriété n’existe pas. Il suffit de suivre la convention.

const superToto = {
    hero = true,
    location: {
        city: {
          name: "Lyon"
        }
    },
    power: {
        psychic: ['telekinesis']
    }
}

if(superToto?.power?.psychic) {
    console.log(superToto.power.psychic) //['telekinesis']
}

console.log(superToto?.location?.country?.name) // undefined

Fini les prises de têtes à vérifier chaque propriété avant de l’utiliser de peur de se prendre une grosse erreur dans la face. L’optional chaining nous permet de faire ça safe, de façon super pratique et super simple à lire. J’étais fou quand j’ai vu ça !



es2020


Nullish coalescing

L’ES2020 vient également avec un nouvel opérateur logique ! Pour bien comprendre de quoi on parle, rappelons rapidement comment fonctionnent les opérateurs logiques existants. Il existe aujourd’hui trois opérateurs logiques : le OU logique (||) le ET logique (&&) et le NON logique (!). On va s’intéresser aux deux premiers.

L’opérateur OU logique va tenter de transformer la première expression en true (les gens parlent de truthy). Si elle peut être convertie, alors cette valeur est renvoyée. Sinon c’est l’autre valeur qui est renvoyée. Examples :

let name = "superToto";
console.log(name || "Toto"); // "superToto"

let name = false;
console.log(name || "Toto"); // "Toto"

let name = undefined;
console.log(name || "Toto"); // "Toto"

let name = null;
console.log(name || "Toto"); // "Toto"

L’opérateur ET logique va tenter de transformer la première expression en false (les gens parlent de falsy). Si elle peut être convertie, alors cette valeur est renvoyée. Sinon c’est l’autre valeur qui est renvoyée. Exemples :

let name = "superToto";
console.log(name && "Toto"); // "Toto"

let name = false;
console.log(name && "Toto"); // false

let name = undefined;
console.log(name && "Toto"); // undefined

let name = null;
console.log(name && "Toto"); // null

Le problème que beaucoup de développeurs rencontrent avec tout ça et la manière dont est traitée undefined et null. Par défaut, Javascript transforme ces valeurs en false alors qu’elles ne sont ni vraies ni fausses, juste vides. Nullish coalescing vient régler ce problème.

Le nouvel opérateur Nullish coalescing, en utilisant la syntaxe ??, va vérifier si le premier opérande est strictement vide (null ou undefined). Si oui, c’est l’autre valeur qui est renvoyée. Sinon, la première valeur est renvoyée. Exemples :

let name = "superToto";
console.log(name ?? "Toto"); // "superToto"

let name = false;
console.log(name ?? "Toto"); // false

let name = undefined;
console.log(name ?? "Toto"); // "Toto"

let name = null;
console.log(name ?? "Toto"); // "Toto"

Boum ! Tu peux gérer tes valeurs “Nullish” comme tu traites les “Falsy” et “Truthy” ! Pas mal non ?



nullish


globalThis

Le futur de Javascript est universel. Ça veut dire qu’on devrait pouvoir prendre et utiliser du code fait sur Node et l’utiliser dans le navigateur (quand ça s’applique évidemment). La plateforme ne devrait plus être un problème.

Mais aujourd’hui, c’est pas le cas. Notamment au niveau des scopes et de comment accéder aux différentes valeurs. Par exemple dans la navigateur tu vas utiliser l’objet window pour accéder au global this. En Node tu vas utiliser global. Etc etc.. Et du coup tu arrives à des solutions comme celle de Mathias Bynens sur son blog.

// A naive attempt at getting the global `this`. Don’t use this!
const getGlobalThis = () => {
  if (typeof globalThis !== 'undefined') return globalThis;
  if (typeof self !== 'undefined') return self;
  if (typeof window !== 'undefined') return window;
  if (typeof global !== 'undefined') return global;
  // Note: this might still return the wrong result!
  if (typeof this !== 'undefined') return this;
  throw new Error('Unable to locate global `this`');
};
const theGlobalThis = getGlobalThis();

Validé par l’ES2020, globalThis vient régler ce problème. C’est une façon simple et unifier d’accéder au global this pour toutes les plateformes. Fini les prises de têtes, tout le monde peut faire de la même façon désormais.

const theGlobalThis = globalThis;


Promise.allSettled

L’ ES2020 introduit également une nouvelle façon de gérer plusieurs promesses à la fois. À la base, on avait juste Promise.all. Promise.all prend un tableau de promesses et retourne une promesse si toutes les promesses du tableau sont résolues. Si une seule promesse reject dans ton tableau, tout est reject immédiatement. Aucune cordialité.

const resolvedPromise = new Promise((resolve, reject) => setTimeout(resolve('superToto'), 500));

const rejectedPromise = new Promise((resolve, reject) => setTimeout(reject(new Error('ERROR')), 500));

Promise.all([resolvedPromise, rejectedPromise]).then(results => console.log(results)).catch(error => console.log(error)); // Error: ERROR

Promise.allSettled va renvoyer un tableau de toutes les promesses avec leurs résultats (résolus ou rejetés). Ça va attendre patiemment que toutes les promesses fassent leurs affaires, sans interrompre quoique-ce, même en cas d’erreur. Ensuite, ça va te renvoyer un joli tableau bien organisé.

const resolvedPromise = new Promise((resolve, reject) => setTimeout(resolve('superToto'), 500));

const rejectedPromise = new Promise((resolve, reject) => setTimeout(reject(new Error('ERROR')), 500));

Promise.allSettled([resolvedPromise, rejectedPromise]).then(results => console.log(results));
// [
//   Object { status: "fulfilled", value: "superToto"},
//   Object { status: "rejected", reason: Error: ERROR}
// ]

On pouvait déjà le faire en hackant un peu via Bluebird. Mais le fait de pouvoir le faire nativement est super agréable ! Très pratique, j’adore !



globalthis


String.prototype.matchAll()

La méthode matchAll() de l’object String est également tagué ES2020. Très simplement, cette nouvelle méthode fait le même taf que match, mais renvoie beaucoup plus d’informations. Avec un match normal, les valeurs qui match sont trouvées et renvoyées tel quelles, sans aucune information utile.

let text = 'Héros des temps modernes: superToto Toto Tata Titi superTiti';
let regex = /Toto/g;
for (const match of text.match(regex)) {
  console.log(match);
}
// Toto
// Toto

Avec matchAll, tu as le droit à beaucoup plus d’informations pour faciliter ton taf. Notamment l’index où se trouve chaque match et l’input. Même le groupe capturant si tu fais une regex plus complexe avec des groupes capturant.

let text = 'Héros des temps modernes: superToto Toto Tata Titi superTiti';
let regex = /Toto/g;
for (const match of text.matchAll(regex)) {
  console.log(match);
}
// ["Toto", index: 31, input: "Héros des temps modernes: superToto Toto Tata Titi superTiti", groups: undefined]
// ["Toto", index: 36, input: "Héros des temps modernes: superToto Toto Tata Titi superTiti", groups: undefined]

Et voilà, j’ai fait le tour de ce dont je voulais te parler sur l’ES2020 ! Il y a des autres changements tagués ici. Mais à mon sens ils sont moins importants que ceux dont je te parle dans cet article. Fin de l’enquête, je te remercie pour la lecture et je finis avec l’épilogue.



es2020


Épilogue

Si tu veux te tenir au courant de ce que fait le TC39 sur ton Javascript, check donc leur GitHub. Ils se passent plein de trucs. Si tu es nouveau en Javascript je te conseille deux articles pour bien comprendre et bien débuter en Javascript. Et sinon je te dis rendez-vous à la prochaine mise à jour de spécification, car je vais faire ça à chaque fois désormais !

Qui me parle ?

jesuisundev
Je suis un dev. En ce moment je suis Backend Développeur / DevOps à Montréal. Le dev est l'une de mes passions et j'écris comme je parle. Je continue à te parler quotidiennement sur mon Twitter. Tu peux m'insulter à cet e-mail ou le faire directement dans les commentaires juste en dessous. Y'a même une newsletter !

11 commentaires sur “ES2020 : les nouveautés dans ton Javascript !”

  1. Quand tu écris “nouvelles feature” je suppose que tu veux dire “nouveautés” 🙂 c est plus cours, et “plus Français”. Sinon j’ai aimé ton article

  2. Hello, petite typo sur matchAll dans le 2eme exemple, tu as réécris match au lieu de matchAll.

    Sinon très bon article comme d’ hab, chaque new feature est bien expliquée 👍

  3. Merci Mehdi pour ce recap ! dis t’as un truc avec Kemar ? 😛
    En tout cas vraiment sympa mais ma nouveauté préférée ça restera l’optional chaining. Un truc de ouf malade qui tue des personnes décédées. Par contre je suis plus partagé avec BigInt. Son utilisation risque d’être exagéré non ? Ca va demander probablement plus de ressources mémoires. Enfin bref c’est mon avis hein 🙂
    Merci m’sieur 🙂

  4. L’optional chaining mais c’est trop bien !!! Purée des kilos de if a la poubelle et fini les typeof boiteux… T’imagine ça avec des clés dynamiques ? Houuuu mais on va pouvoir se faire des boucles bien propres !
    Merci pour ce moment !

T'en penses quoi ?

Your email address will not be published. Required fields are marked *