Retour au blog

ECMAScript 2025 : Les Nouvelles Fonctionnalités JavaScript Que Vous Devez Connaître

Salut HaWkers, ECMAScript 2025 a apporté l'une des mises à jour les plus substantielles de ces dernières années pour JavaScript. Avec les nouveaux Iterator helpers, méthodes de Set, import direct de JSON et améliorations des Promises, le langage continue d'évoluer pour répondre aux besoins des développeurs modernes.

Explorons chacune de ces nouveautés avec des exemples pratiques que vous pouvez commencer à utiliser dès aujourd'hui.

Iterator Helpers : La Grande Vedette

L'ajout le plus attendu d'ES2025 sont les Iterator helpers. Enfin, nous pouvons utiliser des méthodes comme map, filter et reduce directement sur les itérateurs, sans avoir besoin de convertir en arrays.

Pourquoi C'est Important

Avant, travailler avec les itérateurs nécessitait une conversion constante :

// Avant ES2025 - Inefficace pour les grands ensembles
function* genererNombres(limite) {
  for (let i = 0; i < limite; i++) {
    yield i;
  }
}

// Il fallait convertir en array d'abord
const nombres = [...genererNombres(1000000)];
const pairs = nombres.filter(n => n % 2 === 0);
const doubles = pairs.map(n => n * 2);
const somme = doubles.reduce((acc, n) => acc + n, 0);
// Problème : Crée 3 arrays intermédiaires en mémoire

Maintenant avec les Iterator helpers :

// ES2025 - Lazy evaluation, pas d'arrays intermédiaires
function* genererNombres(limite) {
  for (let i = 0; i < limite; i++) {
    yield i;
  }
}

const resultat = genererNombres(1000000)
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((acc, n) => acc + n, 0);

// Traite élément par élément, sans créer d'arrays intermédiaires
console.log(resultat);

Méthodes Disponibles

L'objet Iterator dispose maintenant de méthodes fonctionnelles natives :

// map - Transforme chaque élément
const iterator = [1, 2, 3].values();
const doubles = iterator.map(x => x * 2);
console.log([...doubles]); // [2, 4, 6]

// filter - Filtre les éléments
const nombres = [1, 2, 3, 4, 5].values();
const pairs = nombres.filter(x => x % 2 === 0);
console.log([...pairs]); // [2, 4]

// take - Prend les N premiers éléments
const infini = function* () {
  let i = 0;
  while (true) yield i++;
}();
const premiers = infini.take(5);
console.log([...premiers]); // [0, 1, 2, 3, 4]

// drop - Saute les N premiers éléments
const liste = [1, 2, 3, 4, 5].values();
const sansPremiers = liste.drop(2);
console.log([...sansPremiers]); // [3, 4, 5]

// flatMap - Map + flatten
const imbrique = [[1, 2], [3, 4]].values();
const aplati = imbrique.flatMap(arr => arr.values());
console.log([...aplati]); // [1, 2, 3, 4]

Cas d'Usage Réel : Traitement de Données

// Traitement de lignes d'un gros fichier avec lazy evaluation
async function* lireLignes(fichier) {
  const reader = fichier.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lignes = buffer.split('\n');
    buffer = lignes.pop(); // Garde la ligne incomplète

    for (const ligne of lignes) {
      yield ligne;
    }
  }

  if (buffer) yield buffer;
}

// Utilisation des iterator helpers pour traiter
const csvTraite = lireLignes(grosFichier)
  .filter(ligne => ligne.trim() !== '')
  .filter(ligne => !ligne.startsWith('#'))
  .map(ligne => ligne.split(','))
  .filter(champs => champs.length === 4)
  .map(([nom, email, age, ville]) => ({
    nom: nom.trim(),
    email: email.trim(),
    age: parseInt(age),
    ville: ville.trim()
  }))
  .filter(personne => personne.age >= 18);

// Seulement 100 enregistrements, sans charger tout le fichier
const premiers100 = csvTraite.take(100);

Nouvelles Méthodes de Set

Les Sets JavaScript ont enfin reçu des méthodes qui existaient dans d'autres langages depuis des décennies.

Opérations d'Ensemble

const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([4, 5, 6, 7, 8]);

// union - Tous les éléments des deux ensembles
const union = setA.union(setB);
console.log([...union]); // [1, 2, 3, 4, 5, 6, 7, 8]

// intersection - Éléments communs
const intersection = setA.intersection(setB);
console.log([...intersection]); // [4, 5]

// difference - Éléments dans A qui ne sont pas dans B
const difference = setA.difference(setB);
console.log([...difference]); // [1, 2, 3]

// symmetricDifference - Éléments exclusifs de chaque ensemble
const differenceSymetrique = setA.symmetricDifference(setB);
console.log([...differenceSymetrique]); // [1, 2, 3, 6, 7, 8]

// isSubsetOf - Vérifie si A est contenu dans B
const subset = new Set([4, 5]);
console.log(subset.isSubsetOf(setA)); // true
console.log(subset.isSubsetOf(setB)); // true

// isSupersetOf - Vérifie si A contient B
console.log(setA.isSupersetOf(subset)); // true

// isDisjointFrom - Vérifie s'il n'y a pas d'éléments communs
const setC = new Set([10, 11, 12]);
console.log(setA.isDisjointFrom(setC)); // true
console.log(setA.isDisjointFrom(setB)); // false

Application Pratique : Système de Permissions

class SystemePermissions {
  constructor() {
    this.roles = new Map();
  }

  definirRole(nom, permissions) {
    this.roles.set(nom, new Set(permissions));
  }

  permissionsUtilisateur(rolesUtilisateur) {
    return rolesUtilisateur.reduce((acc, role) => {
      const permissionsRole = this.roles.get(role) || new Set();
      return acc.union(permissionsRole);
    }, new Set());
  }

  verifierAcces(rolesUtilisateur, permissionsNecessaires) {
    const permissionsUtilisateur = this.permissionsUtilisateur(rolesUtilisateur);
    const necessaires = new Set(permissionsNecessaires);

    // Vérifie si l'utilisateur a toutes les permissions nécessaires
    return necessaires.isSubsetOf(permissionsUtilisateur);
  }

  permissionsManquantes(rolesUtilisateur, permissionsNecessaires) {
    const permissionsUtilisateur = this.permissionsUtilisateur(rolesUtilisateur);
    const necessaires = new Set(permissionsNecessaires);

    // Retourne les permissions que l'utilisateur n'a pas
    return necessaires.difference(permissionsUtilisateur);
  }
}

// Utilisation
const systeme = new SystemePermissions();

systeme.definirRole('admin', ['creer', 'lire', 'modifier', 'supprimer', 'gerer_utilisateurs']);
systeme.definirRole('editeur', ['creer', 'lire', 'modifier']);
systeme.definirRole('lecteur', ['lire']);

const utilisateur = ['editeur', 'lecteur'];
const permissionsNecessaires = ['creer', 'supprimer'];

console.log(systeme.verifierAcces(utilisateur, permissionsNecessaires)); // false
console.log([...systeme.permissionsManquantes(utilisateur, permissionsNecessaires)]); // ['supprimer']

Import JSON Directement

Nous pouvons maintenant importer des fichiers JSON directement comme modules ES, sans besoin de fetch ou require.

// config.json
// {
//   "apiUrl": "https://api.exemple.com",
//   "timeout": 5000,
//   "maxRetries": 3
// }

// Avant - Il fallait fetch ou fs
// const config = await fetch('./config.json').then(r => r.json());

// ES2025 - Import direct avec assertion
import config from './config.json' with { type: 'json' };

console.log(config.apiUrl); // https://api.exemple.com
console.log(config.timeout); // 5000

Cas d'Usage

// Importer des données statiques
import traductions from './lang/fr-FR.json' with { type: 'json' };
import routes from './routes.json' with { type: 'json' };
import schema from './validation-schema.json' with { type: 'json' };

// Dynamic import fonctionne aussi
const locale = 'en-US';
const messages = await import(`./lang/${locale}.json`, {
  with: { type: 'json' }
});

Promise.try : Simplifier les Chaînes de Promises

La nouvelle méthode Promise.try permet de démarrer une chaîne de Promises de manière plus propre, traitant aussi bien les fonctions synchrones qu'asynchrones.

// Avant - Il fallait des workarounds
function traiterDonnees(donnees) {
  // Peut lancer une exception synchrone
  const validees = validerDonnees(donnees);

  // Peut retourner une Promise
  return sauvegarderEnBase(validees);
}

// Workaround ancien
Promise.resolve()
  .then(() => traiterDonnees(donnees))
  .then(resultat => console.log(resultat))
  .catch(erreur => console.error(erreur));

// ES2025 - Plus propre
Promise.try(() => traiterDonnees(donnees))
  .then(resultat => console.log(resultat))
  .catch(erreur => console.error(erreur));

Pourquoi Promise.try Est Utile

// Fonction qui peut être synchrone ou asynchrone
function chercherUtilisateur(id) {
  // Validation synchrone peut lancer une erreur
  if (!id || typeof id !== 'number') {
    throw new Error('ID invalide');
  }

  // Cache synchrone
  if (cache.has(id)) {
    return cache.get(id); // Retour synchrone
  }

  // Recherche asynchrone
  return fetch(`/api/users/${id}`).then(r => r.json());
}

// Promise.try traite les deux cas uniformément
Promise.try(() => chercherUtilisateur(userId))
  .then(utilisateur => afficher(utilisateur))
  .catch(erreur => montrerErreur(erreur));

// Équivalent à :
// new Promise(resolve => resolve(chercherUtilisateur(userId)))
//   .then(...)
//   .catch(...);

Float16Array : Nouveau Typed Array

Pour les applications de machine learning et traitement de données qui ont besoin d'efficacité mémoire, le nouveau Float16Array offre des nombres à virgule flottante sur 16 bits.

// Float16Array utilise moitié moins de mémoire que Float32Array
const donnees16 = new Float16Array([1.5, 2.5, 3.5, 4.5]);
const donnees32 = new Float32Array([1.5, 2.5, 3.5, 4.5]);

console.log(donnees16.byteLength); // 8 bytes
console.log(donnees32.byteLength); // 16 bytes

// Utile pour le ML où la précision totale n'est pas nécessaire
function creerTensorEfficace(dimensions, valeurs) {
  const total = dimensions.reduce((a, b) => a * b, 1);
  const donnees = new Float16Array(total);

  for (let i = 0; i < valeurs.length; i++) {
    donnees[i] = valeurs[i];
  }

  return {
    shape: dimensions,
    data: donnees,
    dtype: 'float16'
  };
}

const tensor = creerTensorEfficace([2, 3], [1, 2, 3, 4, 5, 6]);

Améliorations des Expressions Régulières

ES2025 a également apporté des améliorations pour les expressions régulières, incluant le modificateur de flag inline.

// Modifier flag inline - Active/désactive des flags dans des parties de la regex
const regex = /(?i:hello) world/;

// (?i:...) rend seulement "hello" case-insensitive
console.log(regex.test('HELLO world')); // true
console.log(regex.test('hello WORLD')); // false (world doit être en minuscules)

// Utile pour des patterns complexes
const emailRegex = /(?i:[a-z0-9._%+-]+)@(?i:[a-z0-9.-]+)\.(?i:[a-z]{2,})/;

Support Navigateurs

La plupart des fonctionnalités ES2025 sont déjà disponibles dans les navigateurs modernes :

Iterator Helpers :

  • Chrome 122+
  • Firefox 131+
  • Safari 17.4+

Set Methods :

  • Chrome 122+
  • Firefox 127+
  • Safari 17+

JSON Modules :

  • Chrome 123+
  • Firefox 135+
  • Safari 17.2+

Promise.try :

  • Chrome 128+
  • Firefox 132+
  • Safari 18+

Pour les projets qui doivent supporter des navigateurs plus anciens, utilisez des transpileurs comme Babel avec les plugins appropriés.

Migrer Votre Code

Voici une checklist pour commencer à utiliser les nouvelles fonctionnalités :

1. Mettez à jour vos outils :

npm update typescript @babel/core @babel/preset-env

2. Configurez le target TypeScript :

{
  "compilerOptions": {
    "target": "ES2025",
    "lib": ["ES2025", "DOM"]
  }
}

3. Ajoutez des polyfills si nécessaire :

npm install core-js@latest
// Importez uniquement les polyfills nécessaires
import 'core-js/actual/iterator';
import 'core-js/actual/set';
import 'core-js/actual/promise/try';

ECMAScript 2025 représente un pas significatif dans l'évolution de JavaScript, apportant des fonctionnalités qui rendent le code plus expressif et efficace. Si vous voulez approfondir vos connaissances en JavaScript moderne, je recommande de consulter l'article sur Web Workers : Performance avec les Threads où vous apprendrez des techniques avancées d'optimisation.

C'est parti ! 🦅

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires