Map.getOrInsert ES2026: Enfin l Upsert Natif en JavaScript
Salut HaWkers, l une des ajouts les plus pratiques d ES2026 resout enfin un probleme que chaque developpeur JavaScript a rencontre: verifier si une cle existe dans un Map avant d inserer. Les nouvelles methodes getOrInsert et getOrInsertComputed arrivent dans Chrome 145 en Janvier 2026.
Fini le if (!map.has(key)) { map.set(key, value); }. Voyons comment l utiliser.
Le Probleme Actuel
Pourquoi nous avons besoin d upsert.
Pattern Repetitif
Code que tout le monde ecrit:
// AVANT: Regrouper les items par categorie
const groups = new Map();
for (const item of items) {
if (!groups.has(item.category)) {
groups.set(item.category, []);
}
groups.get(item.category).push(item);
}
// Ou la version "intelligente" mais confuse:
for (const item of items) {
const arr = groups.get(item.category) ?? [];
if (!groups.has(item.category)) {
groups.set(item.category, arr);
}
arr.push(item);
}Problemes de Cette Approche
Pourquoi c est mauvais:
// 1. DEUX RECHERCHES sur la meme cle
if (!map.has(key)) { // <- recherche 1
map.set(key, []);
}
map.get(key); // <- recherche 2
// 2. TROP VERBOSE
// 3 lignes pour quelque chose qui devrait etre 1
// 3. SUJET AUX ERREURS
// Facile d oublier de verifier avant
const value = map.get(key);
value.push(item); // TypeError si la cle n existe pas!
// 4. NON ATOMIQUE
// En theorie, une autre operation pourrait interferer
// entre has() et set() (moins pertinent en JS single-thread)
La Solution: getOrInsert
Nouvelle methode native.
Syntaxe
Comment ca fonctionne:
// getOrInsert(key, defaultValue)
// Retourne la valeur existante OU insere et retourne defaultValue
const map = new Map();
// Si 'a' n existe pas, insere 0 et retourne 0
const value = map.getOrInsert('a', 0);
console.log(value); // 0
console.log(map.get('a')); // 0
// Si 'a' existe deja, retourne la valeur existante
map.set('a', 42);
const existing = map.getOrInsert('a', 0);
console.log(existing); // 42 (n ecrase pas)Exemple Pratique: Compteur
Compter les occurrences:
// AVANT (verbose):
const counts = new Map();
for (const word of words) {
if (!counts.has(word)) {
counts.set(word, 0);
}
counts.set(word, counts.get(word) + 1);
}
// APRES (propre):
const counts = new Map();
for (const word of words) {
const count = counts.getOrInsert(word, 0);
counts.set(word, count + 1);
}
// Ou encore plus propre avec objet mutable:
const counts = new Map();
for (const word of words) {
const counter = counts.getOrInsert(word, { count: 0 });
counter.count++;
}Exemple Pratique: Regroupement
Regrouper par propriete:
// AVANT:
function groupBy(items, keyFn) {
const groups = new Map();
for (const item of items) {
const key = keyFn(item);
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key).push(item);
}
return groups;
}
// APRES:
function groupBy(items, keyFn) {
const groups = new Map();
for (const item of items) {
const key = keyFn(item);
groups.getOrInsert(key, []).push(item);
}
return groups;
}
// Utilisation:
const byCategory = groupBy(products, p => p.category);
getOrInsertComputed
Pour les valeurs dynamiques.
Pourquoi Ca Existe
Le probleme avec getOrInsert:
// getOrInsert cree toujours la valeur par defaut
// meme si ce n est pas necessaire
class ExpensiveObject {
constructor() {
console.log('Creation d objet couteux...');
// Operation lourde
}
}
const cache = new Map();
// PROBLEME: ExpensiveObject est cree TOUJOURS
// meme si 'key' existe deja dans le map!
cache.getOrInsert('key', new ExpensiveObject());
// Chaque appel cree un nouvel objet,
// meme quand le cache a deja la valeurSyntaxe
Comment ca fonctionne:
// getOrInsertComputed(key, callbackFn)
// callbackFn n est appele que si key N EXISTE PAS
const cache = new Map();
// Callback s execute seulement si 'key' n existe pas
const value = cache.getOrInsertComputed('key', () => {
console.log('Calcul de la valeur...');
return new ExpensiveObject();
});
// Deuxieme appel: callback NE S EXECUTE PAS
const cached = cache.getOrInsertComputed('key', () => {
console.log('Ceci n apparaitra pas');
return new ExpensiveObject();
});
console.log(value === cached); // trueExemple Pratique: Cache de Calcul
Memoization:
// Cache de resultats calcules
const computationCache = new Map();
function computeExpensive(input) {
return computationCache.getOrInsertComputed(input, () => {
console.log(`Calcul pour: ${input}`);
// Operation lourde
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i * input);
}
return result;
});
}
// Premier appel: calcule
computeExpensive(42); // "Calcul pour: 42"
// Deuxieme appel: cache hit
computeExpensive(42); // (silencieux, retourne le cache)
// Nouvel input: calcule a nouveau
computeExpensive(100); // "Calcul pour: 100"Exemple Pratique: Factory Pattern
Creation paresseuse:
class ConnectionPool {
#connections = new Map();
getConnection(database) {
return this.#connections.getOrInsertComputed(database, () => {
console.log(`Creation de connexion pour: ${database}`);
return new DatabaseConnection(database);
});
}
}
const pool = new ConnectionPool();
// Premier appel: cree la connexion
const conn1 = pool.getConnection('users');
// Meme connexion reutilisee
const conn2 = pool.getConnection('users');
console.log(conn1 === conn2); // true
// Nouvelle connexion pour une autre base
const conn3 = pool.getConnection('products');
console.log(conn1 === conn3); // false
WeakMap l Obtient Aussi
Memes methodes.
Support WeakMap
Fonctionne pareil:
const weakCache = new WeakMap();
class Component {
constructor(id) {
this.id = id;
}
}
const component = new Component(1);
// getOrInsert sur WeakMap
const metadata = weakCache.getOrInsert(component, {
renderCount: 0,
lastUpdate: null,
});
metadata.renderCount++;
// getOrInsertComputed sur WeakMap
const computed = weakCache.getOrInsertComputed(component, () => ({
expensiveData: calculateExpensiveData(component),
}));Cas d Usage: Metadonnees d Objets
Associer des donnees aux objets:
// Metadonnees sans fuite memoire
const objectMetadata = new WeakMap();
function trackObject(obj) {
const meta = objectMetadata.getOrInsert(obj, {
createdAt: Date.now(),
accessCount: 0,
history: [],
});
meta.accessCount++;
meta.history.push(Date.now());
return obj;
}
// Quand obj est garbage collected,
// les metadonnees sont aussi nettoyees
Comparaison Avec les Alternatives
Pourquoi utiliser le natif.
Object.groupBy
Comparaison:
// Object.groupBy (ES2024) - retourne un objet
const grouped = Object.groupBy(items, item => item.category);
// { electronics: [...], clothing: [...] }
// Map.getOrInsert - retourne un Map
const grouped = new Map();
for (const item of items) {
grouped.getOrInsert(item.category, []).push(item);
}
// Quand utiliser chacun:
// - Object.groupBy: resultat final est objet, cles sont des strings
// - Map.getOrInsert: besoin de Map, cles peuvent etre n importe quel typeLodash _.get / _.set
Comparaison:
// Lodash
import _ from 'lodash';
const obj = {};
_.set(obj, 'a.b.c', []);
_.get(obj, 'a.b.c', []).push(item);
// Natif avec Map
const map = new Map();
map.getOrInsert('key', []).push(item);
// Avantages du natif:
// - Pas de dependance
// - Meilleure performance
// - Types corrects (TypeScript)Polyfill Simple
Pour les anciens navigateurs:
// Polyfill pour Map.prototype.getOrInsert
if (!Map.prototype.getOrInsert) {
Map.prototype.getOrInsert = function(key, defaultValue) {
if (!this.has(key)) {
this.set(key, defaultValue);
}
return this.get(key);
};
}
// Polyfill pour Map.prototype.getOrInsertComputed
if (!Map.prototype.getOrInsertComputed) {
Map.prototype.getOrInsertComputed = function(key, callbackFn) {
if (!this.has(key)) {
this.set(key, callbackFn(key));
}
return this.get(key);
};
}
Performance
Pourquoi le natif est meilleur.
Benchmark
Comparaison des approches:
Operation: 1M upserts
Pattern has() + set() + get():
├── Temps: 245ms
├── Operations: 3 par upsert
└── Overhead: recherche dupliquee
getOrInsert natif:
├── Temps: 89ms
├── Operations: 1 par upsert
└── Overhead: minimal
Amelioration: ~2.7x plus rapidePourquoi C est Plus Rapide
Optimisations internes:
// Ancien pattern - 3 operations:
if (!map.has(key)) { // 1. Hash + recherche
map.set(key, value); // 2. Hash + recherche + insertion
}
return map.get(key); // 3. Hash + recherche
// getOrInsert - 1 operation:
map.getOrInsert(key, value); // 1. Hash + recherche + conditionnel
// Optimise en interne par le moteur
Patterns Courants
Refactorisation du code existant.
Compteur de Frequence
Avant et apres:
// AVANT:
function countFrequency(items) {
const freq = new Map();
for (const item of items) {
freq.set(item, (freq.get(item) || 0) + 1);
}
return freq;
}
// APRES:
function countFrequency(items) {
const freq = new Map();
for (const item of items) {
const counter = freq.getOrInsert(item, { n: 0 });
counter.n++;
}
return freq;
}
// Ou avec valeur primitive:
function countFrequency(items) {
const freq = new Map();
for (const item of items) {
const count = freq.getOrInsert(item, 0);
freq.set(item, count + 1);
}
return freq;
}Liste d Adjacence (Graphes)
Avant et apres:
// AVANT:
function buildGraph(edges) {
const graph = new Map();
for (const [from, to] of edges) {
if (!graph.has(from)) graph.set(from, []);
if (!graph.has(to)) graph.set(to, []);
graph.get(from).push(to);
}
return graph;
}
// APRES:
function buildGraph(edges) {
const graph = new Map();
for (const [from, to] of edges) {
graph.getOrInsert(from, []).push(to);
graph.getOrInsert(to, []); // S assure que le noeud existe
}
return graph;
}Multi-Map
Mapper vers plusieurs valeurs:
// AVANT:
class MultiMap {
#map = new Map();
add(key, value) {
if (!this.#map.has(key)) {
this.#map.set(key, new Set());
}
this.#map.get(key).add(value);
}
}
// APRES:
class MultiMap {
#map = new Map();
add(key, value) {
this.#map.getOrInsert(key, new Set()).add(value);
}
}
Timeline de Support
Quand utiliser en production.
Status Actuel (Janvier 2026)
Implementation:
| Navigateur | Status | Version |
|---|---|---|
| Chrome | Stable | 145+ |
| Edge | Stable | 145+ |
| Firefox | En developpement | ~130 |
| Safari | En developpement | ~27 |
| Node.js | Stable | 22+ |
| Deno | Stable | 1.40+ |
| Bun | Stable | 1.1+ |
Recommandation
Quand adopter:
Maintenant (Janvier 2026):
├── Node.js / Deno / Bun: utiliser librement
├── Navigateur moderne: Chrome/Edge ok
├── Tous navigateurs: utiliser avec polyfill
└── TypeScript: types deja disponibles
Q2 2026 (prevu):
├── Firefox stable
├── Safari stable
└── Supprimer polyfill pour la plupart des utilisateurs
Q4 2026 (prevu):
├── Feature baseline
└── Utilisation sans souciConclusion
getOrInsert et getOrInsertComputed sont le type d ajout qui semble petit mais impacte le code reel quotidiennement. Le pattern "verifier si existe, inserer si non, obtenir la valeur" est si courant que le support natif elimine des dizaines de lignes repetitives par projet.
La performance compte aussi: eviter les recherches dupliquees dans le Map peut faire la difference dans les boucles intensives. Et la semantique est plus claire - le nom de la methode dit exactement ce qu elle fait.
Pour les nouveaux projets sur Node.js ou navigateurs modernes, commencez a l utiliser maintenant. Pour les projets qui ont besoin d une large compatibilite, le polyfill de 10 lignes suffit jusqu a ce que tous les navigateurs l implementent.
C est une autre etape de JavaScript vers l ergonomie que d autres langages ont depuis des annees.
Si vous voulez en savoir plus sur les nouvelles fonctionnalites ES2026, consultez notre article sur Import Defer pour une autre addition importante de la version.
Allez, on y va! 🦅
💻 Maitrisez JavaScript pour de Vrai
Les connaissances que vous avez acquises dans cet article ne sont que le debut. Comprendre Map et les structures de donnees est fondamental pour un code efficace.
Investissez dans Votre Avenir
J ai prepare du materiel complet pour que vous maitrisiez JavaScript:
Options de paiement:
- 1x de $4.90 sans interets
- ou $4.90 comptant

