Top-level `await`
Top-level await
enables developers to use the await
keyword outside of async functions. It acts like a big async function causing other modules who import
them to wait before they start evaluating their body.
Top-level await
enables developers to use the await
keyword outside of async functions. It acts like a big async function causing other modules who import
them to wait before they start evaluating their body.
The nullish coalescing proposal (??
) adds a new short-circuiting operator meant to handle default values.
You might already be familiar with the other short-circuiting operators &&
and ||
. Both of these operators handle “truthy” and “falsy” values. Imagine the code sample lhs && rhs
. If lhs
(read, left-hand side) is falsy, the expression evaluates to lhs
. Otherwise, it evaluates to rhs
(read, right-hand side). The opposite is true for the code sample lhs || rhs
. If lhs
is truthy, the expression evaluates to lhs
. Otherwise, it evaluates to rhs
.
Long chains of property accesses in JavaScript can be error-prone, as any of them might evaluate to null
or undefined
(also known as “nullish” values). Checking for property existence on each step easily turns into a deeply-nested structure of if
-statements or a long if
-condition replicating the property access chain:
With the JSON ⊂ ECMAScript proposal, JSON becomes a syntactic subset of ECMAScript. If you’re surprised that this wasn’t already the case, you’re not alone!
In ES2018, ECMAScript string literals couldn’t contain unescaped U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR characters, because they are considered to be line terminators even in that context:
// A string containing a raw U+2028 character.
const LS = '
';
// → ES2018: SyntaxError
// A string containing a raw U+2029 character, produced by `eval`:
const PS = eval('"\u2029"');
// → ES2018: SyntaxError
This is problematic because JSON strings can contain these characters. As a result, developers had to implement specialized post-processing logic when embedding valid JSON into ECMAScript programs to handle these characters. Without such logic, the code would have subtle bugs, or even security issues!
You might already be familiar with the Intl.NumberFormat
API, as it’s been supported across modern environments for a while now.
In its most basic form, Intl.NumberFormat
lets you create a reusable formatter instance that supports locale-aware number formatting. Just like other Intl.*Format
APIs, a formatter instance supports both a format
and a formatToParts
method:
If you’ve written JavaScript for use in a web browser before, you may have used window
to access the global this
. In Node.js, you may have used global
. If you’ve written code that must work in either environment, you may have detected which of these is available, and then used that — but the list of identifiers to check grows with the number of environments and use cases you want to support. It gets out of hand quickly:
Generally, references to objects are strongly held in JavaScript, meaning that as long you have a reference to the object, it won’t be garbage-collected.
const ref = { x: 42, y: 51 };
// As long as you have access to `ref` (or any other reference to the
// same object), the object won’t be garbage-collected.
Currently, WeakMap
s and WeakSet
s are the only way to kind-of-weakly reference an object in JavaScript: adding an object as a key to a WeakMap
or WeakSet
doesn’t prevent it from being garbage-collected.
const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// → metaData
}
// We no longer have a reference to `ref` in this block scope, so it
// can be garbage-collected now, even though it’s a key in `wm` to
// which we still have access.
Let’s say you have an array of dogs, where each dog has a name and a rating. (If this sounds like a weird example, you should know that there’s a Twitter account that specializes in exactly this… Don’t ask!)
// Note how the array is pre-sorted alphabetically by `name`.
const doggos = [
{ name: 'Abby', rating: 12 },
{ name: 'Bandit', rating: 13 },
{ name: 'Choco', rating: 14 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
{ name: 'Falco', rating: 13 },
{ name: 'Ghost', rating: 14 },
];
// Sort the dogs by `rating` in descending order.
// (This updates `doggos` in place.)
doggos.sort((a, b) => b.rating - a.rating);
JavaScript Symbol
s can be given a description upon creation:
const symbol = Symbol('foo');
// ^^^^^
Previously, the only way to access this description programmatically was indirectly through Symbol.prototype.toString()
:
const symbol = Symbol('foo');
// ^^^^^
symbol.toString();
// → 'Symbol(foo)'
// ^^^
symbol.toString().slice(7, -1); // 🤔
// → 'foo'
However, the code is slightly magical-looking, not very self-explanatory, and violates the “express intent, not implementation” principle. The above technique also doesn’t let you distinguish between a symbol with no description (i.e. Symbol()
) and a symbol with the empty string as its description (i.e. Symbol('')
).
Object.fromEntries
is a useful addition to the built-in JavaScript library. Before explaining what it does, it helps to understand the pre-existing Object.entries
API.
Object.entries
The Object.entries
API has been around for a while.
For each key-value pair in an object, Object.entries
gives you an array where the first element is the key, and the second element is the value.
Object.entries
is especially useful in combination with for
-of
, as it enables you to very elegantly iterate over all key-value pairs in an object:
const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]
for (const [key, value] of entries) {
console.log(`The value of ${key} is ${value}.`);
}
// Logs:
// The value of x is 42.
// The value of y is 50.
Unfortunately, there’s no easy way to go from the entries result back to an equivalent object… until now!
Object.fromEntries
The new Object.fromEntries
API performs the inverse of Object.entries
. This makes it easy to reconstruct an object based on its entries:
const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]
const result = Object.fromEntries(entries);
// → { x: 42, y: 50 }
One common use case is transforming objects. You can now do this by iterating over its entries, and then using array methods you might already be familiar with:
const object = { x: 42, y: 50, abc: 9001 };
const result = Object.fromEntries(
Object.entries(object)
.filter(([ key, value ]) => key.length === 1)
.map(([ key, value ]) => [ key, value * 2 ])
);
// → { x: 84, y: 100 }
In this example, we’re filter
ing the object to only get keys of length 1
, that is, only the keys x
and y
, but not the key abc
. We then map
over the remaining entries and return an updated key-value pair for each. In this example, we double each value by multiplying it by 2
. The end result is a new object, with only properties x
and y
, and the new values.