Voltar para o Blog

Map.getOrInsert ES2026: Finalmente Upsert Nativo em JavaScript

Ola HaWkers, uma das adicoes mais praticas do ES2026 finalmente resolve um problema que todo desenvolvedor JavaScript ja enfrentou: verificar se uma chave existe no Map antes de inserir. Os novos metodos getOrInsert e getOrInsertComputed chegam ao Chrome 145 em Janeiro 2026.

Chega de if (!map.has(key)) { map.set(key, value); }. Vamos ver como usar.

O Problema Atual

Por que precisamos de upsert.

Padrao Repetitivo

Codigo que todo mundo escreve:

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

// Ou a versao "esperta" mas 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 Dessa Abordagem

Por que e ruim:

// 1. DUAS BUSCAS na mesma chave
if (!map.has(key)) {  // <- busca 1
  map.set(key, []);
}
map.get(key);         // <- busca 2

// 2. VERBOSE demais
// 3 linhas para algo que deveria ser 1

// 3. PROPENSO A ERROS
// Facil esquecer de verificar antes
const value = map.get(key);
value.push(item);  // TypeError se key nao existe!

// 4. NAO ATOMICO
// Em teoria, outra operacao poderia interferir
// entre has() e set() (menos relevante em JS single-thread)

A Solucao: getOrInsert

Novo metodo nativo.

Sintaxe

Como funciona:

// getOrInsert(key, defaultValue)
// Retorna valor existente OU insere e retorna defaultValue

const map = new Map();

// Se 'a' nao existe, insere 0 e retorna 0
const value = map.getOrInsert('a', 0);
console.log(value);  // 0
console.log(map.get('a'));  // 0

// Se 'a' ja existe, retorna valor existente
map.set('a', 42);
const existing = map.getOrInsert('a', 0);
console.log(existing);  // 42 (nao sobrescreve)

Exemplo Pratico: Contador

Contando ocorrencias:

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

// DEPOIS (limpo):
const counts = new Map();
for (const word of words) {
  const count = counts.getOrInsert(word, 0);
  counts.set(word, count + 1);
}

// Ou ainda mais limpo com objeto mutavel:
const counts = new Map();
for (const word of words) {
  const counter = counts.getOrInsert(word, { count: 0 });
  counter.count++;
}

Exemplo Pratico: Agrupamento

Agrupando por propriedade:

// 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;
}

// DEPOIS:
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

O problema com getOrInsert:

// getOrInsert sempre cria o valor default
// mesmo que nao precise

class ExpensiveObject {
  constructor() {
    console.log('Criando objeto caro...');
    // Operacao pesada
  }
}

const cache = new Map();

// PROBLEMA: ExpensiveObject e criado SEMPRE
// mesmo que 'key' ja exista no map!
cache.getOrInsert('key', new ExpensiveObject());

// Cada chamada cria um novo objeto,
// mesmo quando o cache ja tem o valor

Sintaxe

Como funciona:

// getOrInsertComputed(key, callbackFn)
// callbackFn so e chamado se key NAO existe

const cache = new Map();

// Callback so executa se 'key' nao existe
const value = cache.getOrInsertComputed('key', () => {
  console.log('Computando valor...');
  return new ExpensiveObject();
});

// Segunda chamada: callback NAO executa
const cached = cache.getOrInsertComputed('key', () => {
  console.log('Isso nao vai aparecer');
  return new ExpensiveObject();
});

console.log(value === cached);  // true

Exemplo Pratico: Cache de Computacao

Memoization:

// Cache de resultados computados
const computationCache = new Map();

function computeExpensive(input) {
  return computationCache.getOrInsertComputed(input, () => {
    console.log(`Computando para: ${input}`);
    // Operacao pesada
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.sqrt(i * input);
    }
    return result;
  });
}

// Primeira chamada: computa
computeExpensive(42);  // "Computando para: 42"

// Segunda chamada: cache hit
computeExpensive(42);  // (silencioso, retorna cache)

// Novo input: computa novamente
computeExpensive(100);  // "Computando para: 100"

Exemplo Pratico: Factory Pattern

Criacao preguicosa:

class ConnectionPool {
  #connections = new Map();

  getConnection(database) {
    return this.#connections.getOrInsertComputed(database, () => {
      console.log(`Criando conexao para: ${database}`);
      return new DatabaseConnection(database);
    });
  }
}

const pool = new ConnectionPool();

// Primeira chamada: cria conexao
const conn1 = pool.getConnection('users');

// Mesma conexao reutilizada
const conn2 = pool.getConnection('users');
console.log(conn1 === conn2);  // true

// Nova conexao para outro banco
const conn3 = pool.getConnection('products');
console.log(conn1 === conn3);  // false

WeakMap Tambem Ganha

Mesmos metodos.

Suporte em WeakMap

Funciona igual:

const weakCache = new WeakMap();

class Component {
  constructor(id) {
    this.id = id;
  }
}

const component = new Component(1);

// getOrInsert em WeakMap
const metadata = weakCache.getOrInsert(component, {
  renderCount: 0,
  lastUpdate: null,
});

metadata.renderCount++;

// getOrInsertComputed em WeakMap
const computed = weakCache.getOrInsertComputed(component, () => ({
  expensiveData: calculateExpensiveData(component),
}));

Caso de Uso: Metadados de Objetos

Associando dados a objetos:

// Metadados sem vazar 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;
}

// Quando obj e garbage collected,
// metadados tambem sao limpos

Comparando Com Alternativas

Por que usar o nativo.

Object.groupBy

Comparacao:

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

// Quando usar cada:
// - Object.groupBy: resultado final e objeto, chaves sao strings
// - Map.getOrInsert: precisa de Map, chaves podem ser qualquer tipo

Lodash _.get / _.set

Comparacao:

// Lodash
import _ from 'lodash';

const obj = {};
_.set(obj, 'a.b.c', []);
_.get(obj, 'a.b.c', []).push(item);

// Nativo com Map
const map = new Map();
map.getOrInsert('key', []).push(item);

// Vantagens do nativo:
// - Sem dependencia
// - Performance melhor
// - Tipos corretos (TypeScript)

Polyfill Simples

Para browsers antigos:

// 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 o nativo e melhor.

Benchmark

Comparando abordagens:

Operacao: 1M upserts

Padrao has() + set() + get():
├── Tempo: 245ms
├── Operacoes: 3 por upsert
└── Overhead: busca duplicada

getOrInsert nativo:
├── Tempo: 89ms
├── Operacoes: 1 por upsert
└── Overhead: minimo

Melhoria: ~2.7x mais rapido

Por Que E Mais Rapido

Otimizacoes internas:

// Padrao antigo - 3 operacoes:
if (!map.has(key)) {    // 1. Hash + busca
  map.set(key, value);  // 2. Hash + busca + insercao
}
return map.get(key);    // 3. Hash + busca

// getOrInsert - 1 operacao:
map.getOrInsert(key, value);  // 1. Hash + busca + condicional
// Internamente otimizado pelo engine

Padroes Comuns

Refatorando codigo existente.

Contador de Frequencia

Antes e depois:

// ANTES:
function countFrequency(items) {
  const freq = new Map();
  for (const item of items) {
    freq.set(item, (freq.get(item) || 0) + 1);
  }
  return freq;
}

// DEPOIS:
function countFrequency(items) {
  const freq = new Map();
  for (const item of items) {
    const counter = freq.getOrInsert(item, { n: 0 });
    counter.n++;
  }
  return freq;
}

// Ou com 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 e depois:

// 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;
}

// DEPOIS:
function buildGraph(edges) {
  const graph = new Map();
  for (const [from, to] of edges) {
    graph.getOrInsert(from, []).push(to);
    graph.getOrInsert(to, []);  // Garante que no existe
  }
  return graph;
}

Multi-Map

Mapeando para multiplos 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);
  }
}

// DEPOIS:
class MultiMap {
  #map = new Map();

  add(key, value) {
    this.#map.getOrInsert(key, new Set()).add(value);
  }
}

Timeline de Suporte

Quando usar em producao.

Status Atual (Janeiro 2026)

Implementacao:

Browser Status Versao
Chrome Estavel 145+
Edge Estavel 145+
Firefox Em desenvolvimento ~130
Safari Em desenvolvimento ~27
Node.js Estavel 22+
Deno Estavel 1.40+
Bun Estavel 1.1+

Recomendacao

Quando adotar:

Agora (Janeiro 2026):
├── Node.js / Deno / Bun: usar livremente
├── Browser moderno: Chrome/Edge ok
├── Todos browsers: usar com polyfill
└── TypeScript: tipos ja disponiveis

Q2 2026 (previsto):
├── Firefox estavel
├── Safari estavel
└── Remover polyfill para maioria dos usuarios

Q4 2026 (previsto):
├── Baseline feature
└── Uso sem preocupacao

Conclusao

getOrInsert e getOrInsertComputed sao o tipo de adicao que parece pequena mas impacta codigo real diariamente. O padrao "verificar se existe, inserir se nao, pegar valor" e tao comum que ter suporte nativo elimina dezenas de linhas repetitivas por projeto.

A performance tambem importa: evitar buscas duplicadas no Map pode fazer diferenca em loops intensos. E a semantica e mais clara - o nome do metodo diz exatamente o que faz.

Para novos projetos em Node.js ou browsers modernos, comece a usar agora. Para projetos que precisam de compatibilidade ampla, o polyfill de 10 linhas resolve ate que todos browsers implementem.

E mais um passo do JavaScript em direcao a ergonomia que outras linguagens tem ha anos.

Se voce quer entender mais sobre novas features do ES2026, confira nosso artigo sobre Import Defer para outra adicao importante da versao.

Bora pra cima! 🦅

💻 Domine JavaScript de Verdade

O conhecimento que voce adquiriu neste artigo e so o comeco. Entender Map e estruturas de dados e fundamental para codigo eficiente.

Invista no Seu Futuro

Preparei material completo para voce dominar JavaScript:

Formas de pagamento:

  • 1x de R$27,00 sem juros
  • ou R$27,00 a vista no Pix

📖 Ver Conteudo Completo

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário