Four Built-in JavaScript APIs That Retire Common Lodash Helpers
How Math.sumPrecise, Map getOrInsert, Array.fromAsync and iterator helpers replace Lodash utilities, and the precision and concurrency caveats to know.
This article is published in English.
Installing Lodash used to be one of the first steps in any JavaScript project: it summed numbers, filled maps with defaults and chained lazy transformations. Recent ECMAScript editions ship built-in equivalents for those jobs, so the dependency is often just bundle weight. Below are four native APIs, what each replaces, and where the usual one-line explanation of them is misleading.
Engine support for these recent additions is still rolling out, so check compatibility data for your browsers and Node.js version before dropping a polyfill. For a broader survey, see seven native JavaScript features that replace utility dependencies.
Math.sumPrecise() instead of _.sum()
Numbers in JavaScript are IEEE 754 doubles, which is why 0.1 + 0.2 gives 0.30000000000000004. _.sum() offers no protection: it is a plain loop of +, so it inherits every rounding step. Math.sumPrecise() takes an iterable and returns the sum as if computed exactly, rounding once at the end.
// ❌ The Legacy Lodash/Hack Way
const prices = [0.1, 0.2];
const total = _.sum(prices); // Still prone to precision errors without custom logic
// ✅ The ES2026 Native Standard
const exactTotal = Math.sumPrecise([0.1, 0.2]);
console.log(exactTotal); // 0.3 (Perfect precision)
One correction to the comment above: 0.1 and 0.2 are already inexact before any addition, and their exact sum rounds to 0.30000000000000004, so Math.sumPrecise([0.1, 0.2]) returns that value, not 0.3. What the method removes is error accumulated across many additions: for [1e20, 0.1, -1e20], naive addition yields 0 while Math.sumPrecise returns 0.1. It is not a decimal type, so money still belongs in integer cents or a decimal library. It also throws a TypeError on non-numbers rather than coercing them.
Map.prototype.getOrInsert() instead of has/set/get
Caches and grouping helpers share a familiar pattern: check for a key, store a default if it is missing, then read the value back. getOrInsert(key, defaultValue) does all of that in one call, returning the existing entry or inserting and returning the default.
// ❌ The Legacy Way
const userCache = new Map();
function getUser(id) {
if (!userCache.has(id)) {
userCache.set(id, { status: 'pending' });
}
return userCache.get(id);
}
// ✅ The ES2026 Native Standard
const userCache = new Map();
function getUser(id) {
// Checks for the key. If missing, inserts the fallback and returns it instantly.
return userCache.getOrInsert(id, { status: 'pending' });
}
Note that the fallback object is still constructed on every call, even when it is discarded. If the default is expensive, use getOrInsertComputed(key, callback), which runs the callback only on a miss. Both methods also exist on WeakMap; the Node.js angle is covered in Node.js 26: Temporal, Map upserts and Undici 8.
Array.fromAsync() for draining async iterables
Collecting a paginated API client or a readable stream into an array used to mean a hand-written for await...of loop pushing into an accumulator. Array.fromAsync() consumes an async iterable, awaits each item and resolves to a regular array.
// The ES2026 Native Standard
async function fetchAllPages(asyncPaginationStream) {
// Consumes the entire async stream and resolves into a flat array
const allRecords = await Array.fromAsync(asyncPaginationStream);
return allRecords;
}
An optional second argument maps each item, and its result is awaited too, covering the async map case. Keep in mind that items are awaited sequentially, in order. That suits streams and cursors, but it is not a replacement for Promise.all: if you hold independent promises that should run concurrently, Promise.all is still the tool. And buffering an unbounded stream into memory defeats the purpose of streaming.
Iterator helpers instead of _.chain()
The strongest reason to keep Lodash has been _.chain(), whose lazy evaluation lets a map, filter and take-five over 10,000 items stop once five results exist. Array methods are eager: .map() builds a full intermediate array, .filter() walks it again, and .slice() then discards most of the work.
Iterator helpers, standardized in ES2025, make this lazy natively. .values() returns an iterator, and map, filter and take on it pull one element at a time until toArray() collects the result.
// ❌ The Array Way (Inefficient: Maps and filters all 10,000 items)
const result = massiveArray
.map(x => heavyComputation(x))
.filter(x => x > 100)
.slice(0, 5);
// ✅ The ES2026 Native Lazy Standard
const result = massiveArray
.values() // Converts to an Iterator
.map(x => heavyComputation(x))
.filter(x => x > 100)
.take(5) // Stops exactly when 5 items are found!
.toArray();
Each element passes through the whole pipeline before the next is requested, so heavyComputation runs only until five matches are found. Trade-offs: iterators are single-use, there is no lazy sort or reverse, and for small arrays eager methods are just as fast and easier to debug.
Key takeaways
Math.sumPrecise()removes accumulated rounding error but cannot fix inexact inputs; use integer cents for currency.getOrInsert()replaces has/set/get; prefergetOrInsertComputed()for costly defaults.Array.fromAsync()drains async iterables sequentially; keepPromise.allfor concurrency..values()plus iterator helpers gives lazy pipelines similar to_.chain()without a dependency.