Vérification de utils.js : Six fonctions intégrées qui remplacent les outils manuels
Voyez quelles API natives de JavaScript peuvent remplacer les astuces de clonage profond, les copies de sécurité, les délais manuels, les promesses différées et lodash groupBy, ainsi quels sont les points à considérer.
Sources: 6 Modern JavaScript Features You’re Probably Still Working Around
De nombreux bases de code continuent d’inclure des fonctions d’aide et des importations de bibliothèques pour des problèmes que la plateforme a déjà résolus il y a des années. Ces ajouts se sont faits discrètement, si bien que les habitudes liées aux anciennes solutions n’ont jamais été remises en question. Ci-dessous figurent six fonctions intégrées que vous devriez vérifier dans votre utils.js, avec les problèmes qu’elles résolvent et les limites auxquelles vous devez être conscient avant de supprimer quoi que ce soit.
structuredClone() à la place du traitement JSON
La méthode de copie profonde la plus courante consiste à serialiser un objet puis à le parser à nouveau :
const copy = JSON.parse(JSON.stringify(original));
Cette méthode échoue avec tout ce que JSON ne peut pas représenter : les instances Date reviennent sous forme de chaînes de caractères, les propriétés définies sur undefined sont supprimées, les Map et Set se transforment en objets vides sans aucun avertissement, et une référence circulaire fait que JSON.stringify lance une erreur.
structuredClone() est une fonction globale disponible dans les navigateurs modernes et Node.js, sans nécessiter d’importation. Prenons un objet contenant une date, un ensemble et un tableau associatif :
const original = {
date: new Date(),
items: new Set([1, 2, 3]),
meta: new Map([['key', 'value']]),
};
Le clonage conserve l’intégrité de chacun de ces types, et les structures cycliques sont également copiées correctement :
const copy = structuredClone(original);
// Dates, Sets, Maps, and circular refs all survive intact
Elle utilise l’algorithme de clonage structuré présent derrière postMessage et IndexedDB, ce qui lui permet de gérer des cas limites que les clonages manuels ne couvrent pas. Cependant, elle présente des limites : les fonctions et les nœuds DOM ne peuvent pas être clonés, ce qui génère une erreur, et les instances de vos propres classes reviennent sous forme d’objets ordinaires sans leur prototype.
toSorted() et toReversed() à la place des copies de défense
.sort() et .reverse() modifient l’élément en place, de sorte que trier un tableau partagé par référence le modifie partout. En partant de ce tableau :
const scores = [30, 10, 20];
toSorted() renvoie un nouvel array trié sans modifier l’array d’origine :
const sorted = scores.toSorted((a, b) => a - b);console.log(scores); // [30, 10, 20] — untouched
console.log(sorted); // [10, 20, 30]
toReversed() et toSpliced() suivent le même schéma de retour d’une copie. Si votre code utilise beaucoup de [...arr].sort() pour maintenir l’état immuable, comme c’est souvent le cas avec les reducers React, la propagation n’est plus nécessaire. Une présentation plus détaillée de ces méthodes qui renvoient une copie se trouve à modern JavaScript array methods that replace boilerplate.
Object.hasOwn() au lieu de hasOwnProperty
Appeler cette méthode via l’objet cache un piège :
obj.hasOwnProperty('key'); // breaks if obj = Object.create(null)
Un objet créé avec Object.create(null) n’a pas de prototype, il ne hérite donc jamais de hasOwnProperty et l’appel provoque une erreur TypeError. Il en va de même si un objet définit sa propre propriété sous ce nom. Object.hasOwn() étant une fonction statique, elle ne dépend pas de ce que l’objet hérite :
Object.hasOwn(obj, 'key'); // works regardless of the object's prototype chain
Le changement est mineur, mais il empêche des défaillances qui ne se manifestent qu’en production, lorsque l’appelant passe une structure d’objet non prévue.
AbortSignal.timeout() au lieu d’un timer manuel
Pour fixer une échéance à fetch, il fallait auparavant utiliser un AbortController associé à un setTimeout qui le déclenchait :
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 5000);
Il fallait alors se souvenir de supprimer le timer une fois la requête terminée :
fetch(url, { signal: controller.signal }).finally(() => clearTimeout(id));
Le signal peut désormais comporter sa propre échéance :
fetch(url, { signal: AbortSignal.timeout(5000) });
Une seule ligne, sans nettoyage, sans timer restant. Lorsqu’il se déclenche, la requête est rejetée avec une TimeoutError DOMException, ce qui permet de distinguer un délai d’expiration d’une annulation initiée par l’utilisateur. Pour combiner un délai d’expiration avec une autre source d’annulation, consultez AbortSignal.any() dans la documentation actuelle.
Promise.withResolvers() plutôt que d’utiliser resolve et reject de manière non contrôlée
Parfois, il est nécessaire de résoudre une promesse depuis l’extérieur de son exécuteur. La solution classique consiste à déclarer des variables et à les assigner à l’intérieur du callback du constructeur :
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
Cela fonctionne, mais il est facile de mal interpréter le fait d’extraire des fonctions à l’extérieur d’une fermeture. Promise.withResolvers() renvoie la promesse ainsi que les deux fonctions de résolution en même temps :
const { promise, resolve, reject } = Promise.withResolvers();
Cela s’applique naturellement lorsque vous mettez en file des tâches asynchrones ou que vous enveloppez une API basée sur des événements dans des promesses. Pour un exemple plus complet, consultez la coordination des appels aux outils LLM avec Promise.withResolvers.
Object.groupBy() au lieu de lodash groupBy
Le regroupement des enregistrements selon une clé est l’une des raisons les plus fréquentes d’importer une bibliothèque d’outils. Supposons qu’on ait une liste de personnes :
const people = [
{ name: 'Sam', age: 21 },
{ name: 'Lee', age: 34 },
{ name: 'Kai', age: 21 },
];
Object.groupBy() prend un itérateur et une fonction de rappel, puis retourne un objet contenant un tableau pour chaque clé :
const byAge = Object.groupBy(people, (p) => p.age);
// { 21: [Sam, Kai], 34: [Lee] }
Prêtez attention au nom. Une proposition antérieure appelait cela Array.prototype.group(), mais il a été renommé et déplacé vers la méthode statique Object.groupBy() (ainsi que Map.groupBy() lorsque les clés ne sont pas des chaînes) avant la standardisation, et seules les versions statiques sont incluses. Le résultat possède un prototype nul, et il s’agit de la nouvelle API de cette liste ; vérifiez donc les versions minimales de navigateur et de Node.js prises en charge avant d’éliminer cette dépendance.
Au moment de supprimer les outils d’aide
Aucun de ces changements n’affecte votre architecture, mais chacun élimine un schéma sujet aux erreurs ou une dépendance inutile. Un ordre logique :
- Vérifiez la compatibilité sur caniuse.com ou MDN en fonction des environnements que vous ciblez réellement, car certains d’entre eux sont plus récents que d’autres et les anciens navigateurs peuvent ne pas les supporter.
- Gardez les polyfills tant que votre matrice de compatibilité ne vous permet pas réellement de les supprimer.
Lectures complémentaires
- Formulaires React construits manuellement : inputs contrôlés, validation et états de dépôt — Découvrez le fonctionnement des formulaires React, depuis les inputs contrôlés et les gestionnaires par type jusqu’à la validation, les états de dépôt, les champs dynamiques, les téléchargements et les cas où une bibliothèque de formulaires s’avère utile.