Map.getOrInsert ES2026: Finalmente Upsert Nativo en JavaScript
Hola HaWkers, una de las adiciones mas practicas del ES2026 finalmente resuelve un problema que todo desarrollador JavaScript ha enfrentado: verificar si una clave existe en el Map antes de insertar. Los nuevos metodos getOrInsert y getOrInsertComputed llegan a Chrome 145 en Enero 2026.
Basta de if (!map.has(key)) { map.set(key, value); }. Vamos a ver como usarlo.
El Problema Actual
Por que necesitamos upsert.
Patron Repetitivo
Codigo que todo el mundo escribe:
// ANTES: Agrupando items por categoria
const groups = new Map();
for (const item of items) {
if (!groups.has(item.category)) {
groups.set(item.category, []);
}
groups.get(item.category).push(item);
}
// O la version "inteligente" pero confusa:
for (const item of items) {
const arr = groups.get(item.category) ?? [];
if (!groups.has(item.category)) {
groups.set(item.category, arr);
}
arr.push(item);
}Problemas de Este Enfoque
Por que es malo:
// 1. DOS BUSQUEDAS en la misma clave
if (!map.has(key)) { // <- busqueda 1
map.set(key, []);
}
map.get(key); // <- busqueda 2
// 2. MUY VERBOSE
// 3 lineas para algo que deberia ser 1
// 3. PROPENSO A ERRORES
// Facil olvidar verificar antes
const value = map.get(key);
value.push(item); // TypeError si la clave no existe!
// 4. NO ATOMICO
// En teoria, otra operacion podria interferir
// entre has() y set() (menos relevante en JS single-thread)
La Solucion: getOrInsert
Nuevo metodo nativo.
Sintaxis
Como funciona:
// getOrInsert(key, defaultValue)
// Retorna valor existente O inserta y retorna defaultValue
const map = new Map();
// Si 'a' no existe, inserta 0 y retorna 0
const value = map.getOrInsert('a', 0);
console.log(value); // 0
console.log(map.get('a')); // 0
// Si 'a' ya existe, retorna valor existente
map.set('a', 42);
const existing = map.getOrInsert('a', 0);
console.log(existing); // 42 (no sobrescribe)Ejemplo Practico: Contador
Contando ocurrencias:
// ANTES (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);
}
// DESPUES (limpio):
const counts = new Map();
for (const word of words) {
const count = counts.getOrInsert(word, 0);
counts.set(word, count + 1);
}
// O aun mas limpio con objeto mutable:
const counts = new Map();
for (const word of words) {
const counter = counts.getOrInsert(word, { count: 0 });
counter.count++;
}Ejemplo Practico: Agrupamiento
Agrupando por propiedad:
// ANTES:
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;
}
// DESPUES:
function groupBy(items, keyFn) {
const groups = new Map();
for (const item of items) {
const key = keyFn(item);
groups.getOrInsert(key, []).push(item);
}
return groups;
}
// Uso:
const byCategory = groupBy(products, p => p.category);
getOrInsertComputed
Para valores dinamicos.
Por Que Existe
El problema con getOrInsert:
// getOrInsert siempre crea el valor default
// aunque no lo necesite
class ExpensiveObject {
constructor() {
console.log('Creando objeto costoso...');
// Operacion pesada
}
}
const cache = new Map();
// PROBLEMA: ExpensiveObject se crea SIEMPRE
// aunque 'key' ya exista en el map!
cache.getOrInsert('key', new ExpensiveObject());
// Cada llamada crea un nuevo objeto,
// aunque el cache ya tenga el valorSintaxis
Como funciona:
// getOrInsertComputed(key, callbackFn)
// callbackFn solo se llama si key NO existe
const cache = new Map();
// Callback solo ejecuta si 'key' no existe
const value = cache.getOrInsertComputed('key', () => {
console.log('Computando valor...');
return new ExpensiveObject();
});
// Segunda llamada: callback NO ejecuta
const cached = cache.getOrInsertComputed('key', () => {
console.log('Esto no va a aparecer');
return new ExpensiveObject();
});
console.log(value === cached); // trueEjemplo Practico: Cache de Computacion
Memoization:
// Cache de resultados computados
const computationCache = new Map();
function computeExpensive(input) {
return computationCache.getOrInsertComputed(input, () => {
console.log(`Computando para: ${input}`);
// Operacion pesada
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i * input);
}
return result;
});
}
// Primera llamada: computa
computeExpensive(42); // "Computando para: 42"
// Segunda llamada: cache hit
computeExpensive(42); // (silencioso, retorna cache)
// Nuevo input: computa de nuevo
computeExpensive(100); // "Computando para: 100"Ejemplo Practico: Factory Pattern
Creacion perezosa:
class ConnectionPool {
#connections = new Map();
getConnection(database) {
return this.#connections.getOrInsertComputed(database, () => {
console.log(`Creando conexion para: ${database}`);
return new DatabaseConnection(database);
});
}
}
const pool = new ConnectionPool();
// Primera llamada: crea conexion
const conn1 = pool.getConnection('users');
// Misma conexion reutilizada
const conn2 = pool.getConnection('users');
console.log(conn1 === conn2); // true
// Nueva conexion para otro banco
const conn3 = pool.getConnection('products');
console.log(conn1 === conn3); // false
WeakMap Tambien Gana
Mismos metodos.
Soporte en WeakMap
Funciona igual:
const weakCache = new WeakMap();
class Component {
constructor(id) {
this.id = id;
}
}
const component = new Component(1);
// getOrInsert en WeakMap
const metadata = weakCache.getOrInsert(component, {
renderCount: 0,
lastUpdate: null,
});
metadata.renderCount++;
// getOrInsertComputed en WeakMap
const computed = weakCache.getOrInsertComputed(component, () => ({
expensiveData: calculateExpensiveData(component),
}));Caso de Uso: Metadatos de Objetos
Asociando datos a objetos:
// Metadatos sin leak de memoria
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;
}
// Cuando obj es garbage collected,
// metadatos tambien se limpian
Comparando Con Alternativas
Por que usar el nativo.
Object.groupBy
Comparacion:
// Object.groupBy (ES2024) - retorna objeto
const grouped = Object.groupBy(items, item => item.category);
// { electronics: [...], clothing: [...] }
// Map.getOrInsert - retorna Map
const grouped = new Map();
for (const item of items) {
grouped.getOrInsert(item.category, []).push(item);
}
// Cuando usar cada uno:
// - Object.groupBy: resultado final es objeto, claves son strings
// - Map.getOrInsert: necesita Map, claves pueden ser cualquier tipoLodash _.get / _.set
Comparacion:
// Lodash
import _ from 'lodash';
const obj = {};
_.set(obj, 'a.b.c', []);
_.get(obj, 'a.b.c', []).push(item);
// Nativo con Map
const map = new Map();
map.getOrInsert('key', []).push(item);
// Ventajas del nativo:
// - Sin dependencia
// - Mejor performance
// - Tipos correctos (TypeScript)Polyfill Simple
Para browsers antiguos:
// Polyfill para 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 para 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
Por que el nativo es mejor.
Benchmark
Comparando enfoques:
Operacion: 1M upserts
Patron has() + set() + get():
├── Tiempo: 245ms
├── Operaciones: 3 por upsert
└── Overhead: busqueda duplicada
getOrInsert nativo:
├── Tiempo: 89ms
├── Operaciones: 1 por upsert
└── Overhead: minimo
Mejora: ~2.7x mas rapidoPor Que Es Mas Rapido
Optimizaciones internas:
// Patron antiguo - 3 operaciones:
if (!map.has(key)) { // 1. Hash + busqueda
map.set(key, value); // 2. Hash + busqueda + insercion
}
return map.get(key); // 3. Hash + busqueda
// getOrInsert - 1 operacion:
map.getOrInsert(key, value); // 1. Hash + busqueda + condicional
// Internamente optimizado por el engine
Patrones Comunes
Refactorizando codigo existente.
Contador de Frecuencia
Antes y despues:
// ANTES:
function countFrequency(items) {
const freq = new Map();
for (const item of items) {
freq.set(item, (freq.get(item) || 0) + 1);
}
return freq;
}
// DESPUES:
function countFrequency(items) {
const freq = new Map();
for (const item of items) {
const counter = freq.getOrInsert(item, { n: 0 });
counter.n++;
}
return freq;
}
// O con valor primitivo:
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;
}Adjacency List (Grafos)
Antes y despues:
// ANTES:
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;
}
// DESPUES:
function buildGraph(edges) {
const graph = new Map();
for (const [from, to] of edges) {
graph.getOrInsert(from, []).push(to);
graph.getOrInsert(to, []); // Garantiza que el nodo existe
}
return graph;
}Multi-Map
Mapeando para multiples valores:
// ANTES:
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);
}
}
// DESPUES:
class MultiMap {
#map = new Map();
add(key, value) {
this.#map.getOrInsert(key, new Set()).add(value);
}
}
Timeline de Soporte
Cuando usar en produccion.
Status Actual (Enero 2026)
Implementacion:
| Browser | Status | Version |
|---|---|---|
| Chrome | Estable | 145+ |
| Edge | Estable | 145+ |
| Firefox | En desarrollo | ~130 |
| Safari | En desarrollo | ~27 |
| Node.js | Estable | 22+ |
| Deno | Estable | 1.40+ |
| Bun | Estable | 1.1+ |
Recomendacion
Cuando adoptar:
Ahora (Enero 2026):
├── Node.js / Deno / Bun: usar libremente
├── Browser moderno: Chrome/Edge ok
├── Todos browsers: usar con polyfill
└── TypeScript: tipos ya disponibles
Q2 2026 (previsto):
├── Firefox estable
├── Safari estable
└── Remover polyfill para mayoria de usuarios
Q4 2026 (previsto):
├── Baseline feature
└── Uso sin preocupacionConclusion
getOrInsert y getOrInsertComputed son el tipo de adicion que parece pequena pero impacta codigo real diariamente. El patron "verificar si existe, insertar si no, obtener valor" es tan comun que tener soporte nativo elimina decenas de lineas repetitivas por proyecto.
La performance tambien importa: evitar busquedas duplicadas en el Map puede hacer diferencia en loops intensos. Y la semantica es mas clara - el nombre del metodo dice exactamente lo que hace.
Para proyectos nuevos en Node.js o browsers modernos, empieza a usar ahora. Para proyectos que necesitan compatibilidad amplia, el polyfill de 10 lineas resuelve hasta que todos los browsers implementen.
Es otro paso de JavaScript hacia la ergonomia que otros lenguajes tienen hace anos.
Si quieres entender mas sobre las nuevas features de ES2026, consulta nuestro articulo sobre Import Defer para otra adicion importante de la version.
Vamos con todo! 🦅
💻 Domina JavaScript de Verdad
El conocimiento que adquiriste en este articulo es solo el comienzo. Entender Map y estructuras de datos es fundamental para codigo eficiente.
Invierte en Tu Futuro
Prepare material completo para que domines JavaScript:
Formas de pago:
- 1x de $4.90 sin intereses
- o $4.90 al contado

