Retour au blog

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 valeur

Syntaxe

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);  // true

Exemple 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 type

Lodash _.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 rapide

Pourquoi 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 souci

Conclusion

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

📖 Voir le Contenu Complet

Commentaires (0)

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

Ajouter des commentaires