We saw a lot of great improvements to the JavaScript language in 2020 with the release of ES2020 in June last year. I won't cover all of the changes, but I will briefly mention a few of my favorite features that I use on an (almost) daily basis. I'll also talk about some of the changes that will be coming in ES2021 in June later this year.
This one should be familiar to all of you out there using TypeScript 3.7+. I've certainly been using
it for some time now and it's great to see it officially integrated in ES2020. Essentially, the new ??
operator enables a fallback to a specific value when an undefined
or null
is encountered. Here's an example:
1/** Fallback if "potentiallyNullString" is null or undefined */2const someValue = potentiallyNullString ?? "A default string";34/** Functionally equivalent to */5const someValue =6 potentiallyNullString !== null && potentiallyNullString !== undefined7 ? potentiallyNullString8 : "A default string";
Not only does the nullish coalescing operator make the code block more concise, it also provides some
solid protection for previous use cases of the ||
operator. Here's another example:
1const someValue = emptyString || "A default string";
It's possible that in the above code block, the someValue
variable should default to an empty string.
However in this case, it won't, it will default to A default string
due to the logical or operator
and the fact that the empty string is falsy. The nullish coalescing operator introduces safeguards and
provides great handling for edge cases when encountering falsy values such as 0
, NaN
or ''
.
This one goes hand in hand with the nullish coalsecing operator. Also introduced in TypeScript 3.7,
optional chaining allows code to stop executing if a null
or undefined
is encountered. Optional chaining
is even more useful when combined with the nullish coalsecing operator to set defaults. Here's an example:
1/** Optionally call a function or return an empty array */2const result = state.value?.action() ?? [];
Optional chaining also simplifies code that checks for properties on objects:
1/** Before */2if (state && state.value && state.value.x) { ... }34/** After */5if (state?.value?.x) { ... }
Similar to Promise.all
or Promise.race
, this enhancement runs all promises regardless of the results.
This is especially useful when performing several network requests in parallel, where some of the requests
might either fail or return an error, and others might succeed.
Promise.allSettled
shines when each request is not dependent on another to complete successfully.
1/** Perform the network requests */2const requests = await Promise.allSettled([3 fetch(...),4 fetch(...),5 fetch(...)6]);78/** Split the requests into successes and failures */9const [errors, results] = requests.reduce((accum, request) => {10 if (request.status === 'rejected') accum[0].push(request);11 else if (request.status === 'fulfilled') accum[1].push(request);12 return accum;13}, [[], []]);
Three new operators are proposed for the release in mid of this year. The &&=
, ||=
, and ??=
operators all allow conditional variable assignment. Here are a few examples:
1/** Assign y to x if x is falsy */2const x ||= y;34/** Assign y to x if x is truthy */5const x &&= y;67/** Assign y to x if x is null/undefined */8const x ??= y;
I don't immediately see myself using these operators, but I will keep them in mind. Personally,
I think the last one, the ??=
would be the most useful.
Yes! I feel like I have been constantly reimplementing this across different projects, so it is great
to see it added as a core functionality. As you might have guessed, replaceAll
replaces all
occurances of a string/pattern with the second parameter passed into it. Consider it a sequal to the
built in replace
function for strings.
1const str = "Bob is a builder. Bob builds the best fences.";23const first = str.replace("Bob", "Tom"); // Tom is a builder. Bob builds the best fences.4const second = str.replaceAll("Bob", "Tom"); // Tom is a builder. Tom builds the best fences.
Similar to Promise.allSettled
but with a twist. The new .any()
method on Promise
takes an array of
promises and resolves with the first (read: fastest) successful promise. Otherwise, it rejects if all promises reject.
On the surface, this doesn't seem all that useful, however one interesting use case could be loading external
resources from the fastest server. Consider a browser client application with the following:
1const resource = await Promise.any([2 fetch("https://us.example.com/resource-a").then((response) =>3 response.json()4 ),5 fetch("https://uk.example.com/resource-a").then((response) =>6 response.json()7 ),8 fetch("https://jp.example.com/resource-a").then((response) =>9 response.json()10 ),11]);
Notice how in the example above, we are requesting a resource from a region specific endpoint. Using Promise.any
enables this kind of functionality, where the application interacts with the fastest server relative to
the geographical location of the client.
Lastly, there are two other new features introduced. The first, the Numeric separator is a readability improvement for displaying large numbers, i.e. 123456789 can become 123_456_789 while still behaving as a number.
The second feature, Weakref is more complex and probably deserving of its own article. The TC39 proposal even mentions that it should be avoided if possible so I won't be covering it here.
That is mostly it! I'll be looking forward to next year and seeing what will make it into ES2022.