Report: What’s New in ECMAScript and JavaScript for 2020

3 Feb 2020 11:41am, by

Since 2015, ECMAScript — the formal specification of the JavaScript language — has been getting annual updates that come out every June. These updates include all proposals that have reached stage 4: finished proposals that have been signed off by all the ECMAScript editors, shipped in at least two compatible implementations and passed acceptance testing.

After the backlog of proposals dealt within ECMAScript 2015, the annual updates have tended to be smaller, with a mix of new features, syntax improvements and updates that cover slight omissions in the standard.

ECMAScript 2019 has some of those updates to clean up the language internally, including small syntax tweaks making it a release that’s about language correctness with some improvements for developer productivity.

Common Sense and Stability in ECMAScript 2019

That includes making JSON a true subset of ECMAScript by adding the line and paragraph separators JSON string literals and having JSON.stringify return well-formed UTF-8 (in some situations it would return some UTF-16 characters). Few developers will be typing any of those by hand, but it simplifies embedding JSON and emitting JavaScript from metaprogramming tools.

There are new trimStart and trimEnd methods to remove white space before or after a string: if you’ve been using trimLeft and trimRight for this the new methods are less confusing for right-to-left languages. There’s a new built-in way to change an array of key-value pairs to an object, Object.fromEntries, rather than having to write extra code to do that.

The ECMAScript specification now requires that Array.prototype.sort is a stable sort: given the same input array, sorting will always produce the same output array, even if there are two identical items in the set with the same sort position.

“Previously the spec was more hands-off on actual ordering, so developers had to make sure they didn’t depend on the exact ordering that would come out of Array.prototype.sort because that would be different in a different ECMAScript implementation, or even different depending on array size and many other factors,” TC39 co-chair Brian Terlson explained to us. That meant a JavaScript engine could change the sort algorithm is used depending on how long a list was, which could improve efficiency but that would also mean you get a different sort order depending on where you are in your program: V8 used to use an unstable QuickSort for arrays with more than ten elements and now all V8 array sorts use stable TimSort.

This makes new code simpler to write, but can affect old code; mostly it means you won’t need logic you put in to make sort ordering constant and you might have to update your tests.

Optional catch binding when writing try/catch statements is a small, common-sense syntax change that will remove a lot of developer frustration because previously you’ve had to include code you don’t really need, just to avoid errors from ESLint and TypeScript. “Previously you couldn’t omit the catch binding [for an error parameter] even if you didn’t care about it, so you’d have to deal with linting problems because you declared a catch variable that you then didn’t use — because you didn’t care about it.”

The new flat and flatMap methods may be the most useful change in ECMAScript 2019. Flat was originally suggested as “flatten” because it flattens an array (if any of the values in the array are themselves arrays, the values in them are extracted into the top-level array) but that conflicted with the widely used MoTools library. flatMap first iterates over the array (map) and then flattens it (flat) in case the map operation has created a nested array (the name makes more sense when you remember that it’s borrowed from functional languages where it would be written as flat(map(…)), Terlson tells us).

“That’s a very common pattern that’s present in a lot of different language ecosystems and it’s in a lot of JavaScript library implementations,” Terlson pointed out; it’s also much more efficient. “If you can do the flattening and the mapping in one operation you’re allocating fewer intermediate arrays and you’re only iterating over the array once instead of twice. So it’s significantly more efficient to use flatMap when you want to use both flat and map — which turns out to be a very common thing to do!”

ECMAScript 2020 Goes Big on Numbers

There’s some spec cleanup in ECMAScript 2020 too, like making the enumeration order of for (x in y) fixed rather than implementation-dependent. “This is getting all over the properties of an object, and all of its prototype objects’ properties, recursively,” Terlson explained. “JavaScript engines are generally speaking very reluctant to change this code because it’s a very common thing that JavaScript developers do, and it’s very sensitive performance-wise.” But even though ECMAScript didn’t specify the enumeration order to let engines optimize for performance, it turns out most of the engines have already aligned on the same order, so the spec now reflects that.

In terms of features, he called ECMAScript 2020 is a much bigger update than 2019. “We have a lot more syntax proposals in there, and we have big things like BigInt.”

JavaScript has long needed a better way to handle large numbers without using specific libraries and BigInt is the first step in that: a new primitive for representing arbitrary precision integers. Currently, if you have an integer larger than 253 in JavaScript then you start losing precision, which means you can’t represent extremely large numbers or high-precision timestamps; that’s led to some long-standing bugs like Node.js deleting the wrong file because it thinks they have the same Inode number (which is stored as a 64-bit integer in Windows).

The 64-bit integers are so useful that the ECMAScript community considered adding them directly, Terlson said. “They are just super, super useful in a lot of different contexts. You need 64-bit integers when you’re talking to various platform APIs. BigInt makes it a lot easier for JavaScript developers to interoperate with functions in platforms that need 64-bit integers for cryptography, for native APIs and for bindings with other languages like talking with a Node native module that’s calling a system API that needs a 64-bit integer. Cryptography especially is an area where you’re often passing around 64-bit integers. WebAssembly interop was a big reason to do big integers. And just having big integers opens up a whole new use case for JavaScript where you’re doing math and operations on giant, giant integers and not losing any precision in your integer operations.”

Decimal and rational might be the next number types to come to ECMAScript in future. “A big thing JavaScript is missing is some sort of decimal type: an arbitrary length decimal number, so we might be looking at BigDecimal. Rationals are another way of representing arbitrary-precision numbers: they’re nice because they let you do operations that typically lose precision without losing any precision at all. The rational takes care of that just by storing the numerator and the denominator separately.”

Adding these kinds of fundamental types is part of the evolution of JavaScript, Terlson suggested. “JavaScript is a very nice general-purpose language now and a lot of the things we’re doing, like big integers, are expanding what JavaScript is good for. As the years go on and JavaScript gets pushed in more directions both high level, and also low level like working on hardware, we’re going to get some new constraints coming online. That’s also motivating proposals like the new number types because we have to interoperate with more different systems.”

Something else that helps with the increasingly wide variety of places JavaScript now runs is globalThis. Writing portable, cross-platform ECMAScript code that references the global object is difficult because it has different names on different platforms; in a browser, it can be “window” or “self” or “this,” on Node.js it’s “global” or “this” and “this” is available in a shell but not in a module where it’s “undefined.”

“That makes it really frustrating when you’re writing libraries that target both Node.js and browsers,” Terlson said.

It proved surprisingly difficult to find a name that didn’t conflict with code that’s already in use, which is why the name is globalThis rather than just global.

Chaining and Coalescing

ECMAScript 2020 also adds some powerful operators that are already in languages like Swift and C# with syntax that simplifies some common tasks.

“Optional chaining lets you say, ‘if this object exists, then I want to access this property, otherwise just gives me undefined’ and it prevents you from getting the ‘undefined is not an object’ error. It turns multiple lines of spaghetti code into one line of just ‘?, ?, ?.’ And this comes up all the time, especially when you’re dealing with like JSON responses from a service where a record might be present or not: you just want to get it if it’s there and if it’s not, you want it to be undefined right. This is a feature that developers have been clamoring for a number of years and we finally delivered it,” Terlson suggested.

Nullish coalescing is a similar operator, represented as “??,” for handling values that aren’t null or undefined (which are already handled by the || operator) but are some other form of “nullish” or “falsey” values like zero. “It’s basically a better version of what JavaScript developers do today with logical OR to set defaults. You’ll say const NaN = NaN or default,” Terlson said. “The problem is that if someone passes NaN as zero or a string or Boolean false or other falsely values that aren’t undefined or null, they would get the default — and users found this surprising and frustrating and it’s a big source of bugs.”

Drop in the nullish coalescing operator and you only get the default value if what’s returned is either undefined or null, giving you a better way of applying defaults.

Making More Promises

ECMAScript 2021 may include the ability to use await in the root of your program, instead of having to write an async function to put it in, but ECMAScript 2020 will extend import() so that you can load a module asynchronously, which means that you can do conditional or lazy loading by getting a promise for the module, and not load any modules that aren’t actually needed. This is so useful that the main browsers already support it.

“Dynamic import is useful for polyfilling because sometimes you need to check the environment to see if you need a polyfill, and if you do you can go ahead and import it. You can also do things like localized modules, ‘foo module-en’ for English and ‘foo module-sp’ for Spanish, where you get the user locale and then use dynamic import to load the appropriate module.”

Promise.allSettled is a useful addition to the two Promise combinators added in ECMAScript 6. Promise.all resolves if all the promises you pass into it complete successfully and rejects as soon as the first promise is rejected. Promise.race returns the first promise to get fulfilled, whether it succeeds or not (winning the race). But suppose you want to get the results of all the promises you pass in, even if one of them fails? Promise.allSettled waits for all the promises to be settled but it doesn’t matter whether they’re resolved or rejected: it returns an array with all the results.

There’s a fourth option found in a lot of promise libraries, Promise.any, which is the inverse of Promise.all, returning the first promise to succeed or list of the error codes if they all fail; that’s useful if any of the promises will do – but you need to know what went wrong if none of them succeed. That’s currently a stage 3 proposal, so it might be ready for ECMAScript 2021.

One final ECMAScript 2020 feature fixes something that’s been confusing developers for many years with another of those small language improvements that should remove enormous amounts of frustration when working with regular expressions.

When you call the current String.prototype.match the result depends on whether the regex you pass in has the -g global flag or not. Developers have been finding that confusing for Plus, if you pass in a string that you want to match, it will only ever march one instance of the string, but finding all the occurrences of a string is a very common use case (and running code to do that without the global flag can produce an infinite loop).

The way match works with the global flag doesn’t change, but String.prototype.matchAll makes the process much more explicit, Telson explained. ”If you pass in a regular expression without the global flag it gives you an error right away. ‘This doesn’t make sense: you’ve told me to match all but you’ve given me a regex that only matches one thing.’ So that helps users use the API correctly, But the biggest benefit is if you pass in a string, it will match all occurrences of that string and you get an array of all the results.”

Some proposals that are close to being finished but seem unlikely to be included in ECMAScript 2020 will likely be ready for the 2021 specification, like the long-awaited private class fields (which have just arrived in TypeScript). There’s been an on-going discussion about how these should work (since 2017 in fact), but it looks like a consensus has been reached on what will be another major feature coming to JavaScript in the near future.

This post has been revised for additional clarity.

Feature image by Lorri Lang from Pixabay.

A newsletter digest of the week’s most important stories & analyses.