ES2019 — The Pragmatic Update

Every year since ES2015, the TC39 committee has released a new edition of the ECMAScript specification. While ES2015 (ES6) was a revolutionary overhaul of the language, subsequent releases have been more incremental, focusing on practical improvements that address common pain points. ES2019 (also known as ES10) continues this tradition with a set of features that are small individually but collectively make a meaningful difference in code quality and readability.

At StrikingWeb, we have been using these features in production code since browser support stabilized in mid-2019. Here is a breakdown of each feature with practical examples from our projects.

Array.prototype.flat()

Before ES2019, flattening nested arrays required utility libraries like Lodash or custom recursive functions. The new flat() method does this natively:

// Flatten one level deep (default)
const nested = [[1, 2], [3, 4], [5, 6]];
nested.flat(); // [1, 2, 3, 4, 5, 6]

// Flatten deeply nested arrays
const deep = [1, [2, [3, [4, [5]]]]];
deep.flat(Infinity); // [1, 2, 3, 4, 5]

// Real-world: Merge category products from an API response
const categories = [
  { name: 'Electronics', products: ['Laptop', 'Phone'] },
  { name: 'Books', products: ['Novel', 'Textbook'] }
];
const allProducts = categories.map(c => c.products).flat();
// ['Laptop', 'Phone', 'Novel', 'Textbook']

The depth parameter controls how many levels of nesting to flatten. The default is 1, and Infinity flattens all levels. In practice, we most commonly use flat() when aggregating data from nested API responses or when working with arrays of arrays produced by mapping operations.

Array.prototype.flatMap()

The flatMap() method combines map() and flat(1) into a single operation. It maps each element through a function and then flattens the result by one level. This is more efficient than calling map().flat() separately because it only traverses the array once:

// Without flatMap
const sentences = ['Hello world', 'Good morning'];
const words = sentences.map(s => s.split(' ')).flat();
// ['Hello', 'world', 'Good', 'morning']

// With flatMap — single pass, same result
const words2 = sentences.flatMap(s => s.split(' '));
// ['Hello', 'world', 'Good', 'morning']

// Practical: Expanding orders into line items
const orders = [
  { id: 1, items: ['Widget A', 'Widget B'] },
  { id: 2, items: ['Widget C'] }
];
const allItems = orders.flatMap(order =>
  order.items.map(item => ({ orderId: order.id, item }))
);
// [
//   { orderId: 1, item: 'Widget A' },
//   { orderId: 1, item: 'Widget B' },
//   { orderId: 2, item: 'Widget C' }
// ]

We use flatMap() extensively when transforming data structures in e-commerce projects — expanding order line items, denormalizing product variants, and aggregating search results from multiple sources.

Object.fromEntries()

This method is the inverse of Object.entries(). It takes an iterable of key-value pairs and returns a new object. This is incredibly useful for transforming objects by converting them to entries, manipulating the entries, and converting back:

// Basic usage
const entries = [['name', 'StrikingWeb'], ['founded', 2018]];
Object.fromEntries(entries);
// { name: 'StrikingWeb', founded: 2018 }

// Convert URLSearchParams to an object
const params = new URLSearchParams('category=tech&sort=date&page=1');
const paramsObj = Object.fromEntries(params);
// { category: 'tech', sort: 'date', page: '1' }

// Filter object properties
const config = { debug: true, verbose: false, env: 'production', port: 3000 };
const booleanConfig = Object.fromEntries(
  Object.entries(config).filter(([key, value]) => typeof value === 'boolean')
);
// { debug: true, verbose: false }

// Transform object values
const prices = { widget: 10, gadget: 20, tool: 15 };
const withTax = Object.fromEntries(
  Object.entries(prices).map(([key, value]) => [key, value * 1.18])
);
// { widget: 11.8, gadget: 23.6, tool: 17.7 }

Before Object.fromEntries(), transforming objects required either reduce() (which is verbose and error-prone) or a library function. This method makes object transformation clean and composable.

String.prototype.trimStart() and trimEnd()

JavaScript has had trim() for years, but there was no standard way to trim only one side of a string. ES2019 adds trimStart() (also aliased as trimLeft()) and trimEnd() (aliased as trimRight()):

const input = '   hello@strikingweb.in   ';
input.trimStart(); // 'hello@strikingweb.in   '
input.trimEnd();   // '   hello@strikingweb.in'
input.trim();      // 'hello@strikingweb.in'

We use trimEnd() frequently when building formatted text output or log messages where trailing whitespace needs removal but leading indentation must be preserved.

Optional Catch Binding

In ES2019, you no longer need to declare the error parameter in a catch block if you do not use it. This is a small syntactic improvement, but it eliminates the common pattern of declaring a variable you never reference:

// Before ES2019 — forced to declare 'err' even if unused
try {
  JSON.parse(userInput);
} catch (err) {
  return defaultValue;
}

// ES2019 — omit the parameter entirely
try {
  JSON.parse(userInput);
} catch {
  return defaultValue;
}

This is particularly useful when you are using try/catch purely for control flow — checking whether an operation succeeds or fails without needing to inspect the error itself. Feature detection, JSON parsing validation, and existence checks are common use cases.

Symbol.prototype.description

Symbols can now expose their description directly through a description property, rather than requiring conversion to a string and parsing:

const sym = Symbol('unique_identifier');
sym.description; // 'unique_identifier'

// Previously required
String(sym); // 'Symbol(unique_identifier)'

While symbols are less commonly used in everyday application code, this improvement is valuable when building libraries, framework internals, or debugging tools where symbol descriptions serve as identifiers.

Function.prototype.toString() Revision

The toString() method on functions now returns the exact source text of the function, including comments and whitespace. Previously, engines could return a normalized version that stripped comments and reformatted the code:

function /* this is a comment */ greet(name) {
  return `Hello, ${name}`;
}

greet.toString();
// "function /* this is a comment */ greet(name) {\n  return `Hello, ${name}`;\n}"

This is primarily useful for metaprogramming, code analysis tools, and documentation generators that need to display the original source code of functions.

Well-Formed JSON.stringify()

ES2019 fixes a subtle bug where JSON.stringify() could produce invalid Unicode strings. Previously, lone surrogate characters (code points U+D800 through U+DFFF) would be output as-is, producing invalid Unicode. Now they are escaped as Unicode escape sequences, ensuring the output is always valid Unicode and valid JSON.

This is a behind-the-scenes fix that most developers will never notice, but it eliminates a class of subtle bugs that could occur when serializing strings containing unusual characters — particularly relevant when handling user-generated content from diverse language inputs.

Stable Array.prototype.sort()

ES2019 requires Array.prototype.sort() to be a stable sort. A stable sort preserves the relative order of elements that compare as equal. Previously, the specification did not require stability, and some engines (notably V8 for arrays with more than 10 elements) used an unstable sorting algorithm:

const items = [
  { name: 'A', priority: 1 },
  { name: 'B', priority: 2 },
  { name: 'C', priority: 1 },
  { name: 'D', priority: 2 }
];

items.sort((a, b) => a.priority - b.priority);
// Guaranteed: A comes before C, B comes before D
// (preserving original order within each priority group)

For web applications that sort tables, lists, or search results, stable sorting is essential for predictable behavior. Users expect that items with equal sort values maintain their original order.

Using ES2019 Today

All ES2019 features are supported in Chrome 73+, Firefox 62+, Safari 12.1+, and Edge 79+. For projects that need to support older browsers, Babel can transpile these features with the appropriate presets. If you are using @babel/preset-env with a target of >0.25%, these features will be transpiled automatically when needed.

At StrikingWeb, we have adopted ES2019 features across our codebase. The combination of flat(), flatMap(), and Object.fromEntries() alone has simplified dozens of data transformation functions, making our code shorter, more readable, and easier to maintain.

Share: