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 valorSintaxe
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); // trueExemplo 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 tipoLodash _.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 rapidoPor 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 preocupacaoConclusao
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

