Skip to content

Latest commit

 

History

History
424 lines (293 loc) · 16.6 KB

ES2021.MD

File metadata and controls

424 lines (293 loc) · 16.6 KB

ES2021 "ES12" credit1 credit2

See the ES2021 standard for full specification of the ECMAScript 2021 language.

ES2021 includes the following new feature proposals:


Logical Assignment Operators

The new logical assignment operators&&=, ||=, and ??=—combine logical operations with the assignment operation. These new operators are similar to the existing logical operators and will allow you to assign default values to variables.

Logical OR Assignment Operator (||=)

Similar to the logical OR operator, the logical OR assignment operator is a short-circuit operation. The expression x ||= y is identical to the expression x || (x = y). This means that y will be assigned to x, if and only if, x constitutes a falsy value. Otherwise, it will preserve its initial value.

const setDefaultPassword = () => {
  // Perform some logic to set up a default password
  return 'defaultPassword'
}

const jane = {
  name: 'Jane Doe',
  password: 'Abc123'
}

const john = {
  name: 'John Doe',
  password: ''
}

jane.password ||= setDefaultPassword()
john.password ||= setDefaultPassword()

console.log(jane.password)
// Prints 'Abc123'

console.log(john.password)
// Prints 'defaultPassword'

Logical AND Assignment Operator (&&=)

The logical AND assignment operator is the antithesis of the logical OR assignment operator. In the expression x &&= y, y is assigned to x, if and only if, x is a truthy value. Otherwise, it preserves its initial value.

const updateOffice = () => {
  return 2
}

const jane = {
  id: 1,
  name: 'Jane Doe',
  office: 4
}

const john = {
  id: 2,
  name: 'John Doe',
  office: undefined
}

jane.office &&= updateOffice()
john.office &&= updateOffice()

console.log(jane.office)
// Prints 2

console.log(john.office)
// Prints undefined

Logical Nullish Assignment Operator (??=)

The logical nullish assignment operator operates in such a way that in the expression x ??= y, it only assigns y to x, if x is nullish (i.e., if x is either null or undefined).

const createEmail = () => {
  // Perform some logic to create an email
  return '[email protected]'
}

const jane = {
  id: 1,
  name: 'Jane Doe',
  email: '[email protected]'
}

const john = {
  id: 2,
  name: 'John Doe'
}

jane.email ??= createEmail()
john.email ??= createEmail() // john.email is undefined

console.log(jane.email)
// Prints '[email protected]'

console.log(john.email)
// Prints '[email protected]'

String.replaceAll()

String.replaceAll() is a suitable method that addresses a particular shortage in String.replace()—the inability to replace all the occurrences of a pattern with a new string (to do that, one had to use regular expressions).

Now, with the introduction of String.replaceAll() to the language, we can easily replace all the occurrences of a given substring at once.

The String.replaceAll() method returns a new string in which all occurrences of a pattern are replaced by a replacement passed to it. The first parameter, a pattern, can either be a string or a regex pattern, and the second parameter, a replacement, can either be a string or a function that creates a new string to replace the pattern. This method operates without mutating the original string.

const sentence =
  'Interestellar is a great movie. Everybody should watch the movie at least once.'

sentence.replace('movie', 'film')
// Returns 'Interestellar is a great film. Everybody should watch the movie at least once.'
// Only the first occurrence of 'movie' is replaced.

sentence.replaceAll('movie', 'film')
// Returns 'Interestellar is a great film. Everybody should watch the film at least once.'
// All occurrences of 'movie' are replaced.

sentence.replaceAll('movie', () => 'flick')
// Returns 'Interestellar is a great flick. Everybody should watch the flick at least once.'
// All occurrences of 'movie' are replaced with the string returned by the callback function.

Promise.any() and AggregateError

Promise.any([promise1, promise2, promise3, ...]).then(...do something)

Promise.any() is a new promise method that takes in an iterable of Promise objects and, as soon as one of the promises in the iterable fulfills, it returns a single promise that resolves to the value from that first fulfilled promise. If no promises in the iterable fulfill (i.e., if all of the given promises are rejected), then the returned promise is rejected with an AggregateError—a new subclass of Error that groups together individual errors. In essence, the Promise.any() method is the opposite of Promise.all().

const promise1 = Promise.reject(0)
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'))
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'))

const promises = [promise1, promise2, promise3]

Promise.any(promises).then((value) => console.log(value))
// Prints 'quick'

Now, the scenario in which none of the promises passed to Promise.any() fulfills (which also takes into account the case where the passed iterator is empty), we need to catch the exception and handle it.

const promise = new Promise((_, reject) => reject(new Error('Ooops!')))

try {
  async function test() {
    const result = await Promise.any([promise])

    console.log(result)
  }

  test()
} catch (error) {
  console.error(error.errors)
  // Logs the object Promise {<rejected>: AggregateError: All promises were rejected}
  // Throws the error caught (in promise) AggregateError: All promises were rejected
}

For the sake of simplicity, we passed a to-be-rejected promise to Promise.any(). As a result, the above code logs the following error in the console.

Numeric Separator

ES2021 introduced the numeric separator to improve the readability of numeric literals by creating a visual separation between groups of digits. As a result, we can use the underscore (_) character to separate groups of digits, just like we use commas to separate numbers in writing.

This new feature of the language helps us circumvent the difficulty the human eye faces to quickly parse large numeric literals (especially when they involve long repetition of digits), so now it could be easier for developers to get the correct value or order of magnitude of long and/or confusing numeric literals in their code.

Let's take a look at some examples:

let x = 1000000000 // Is this a billion? a hundred millions? Ten millions?
let y = 101475938.38 // What scale is this? What power of 10?
let z = 0.000001 // Is this a millionth? a billionth?

// Numeric separators to the rescue!
x = 1_000_000_000 // This is a billion
y = 101_475_938.38 // This is a hundred million plus change
z = 0.000_001 // This is a millionth

Intl.ListFormat

Before exploring how this new constructor works, let's recall what the Intl object is: a namespace for the ECMAScript Internationalization API, which supplies a collection of helper methods to support internalization efforts, like language-sensitive string comparison, number formatting as well as date and time formatting.

Consequently, the Intl.ListFormat object enables language-sensitive list formatting. Its constructor,ListFormat, creates and returns a formatter object that—depending on the configuration passed upon creation—will join lists of strings using the best localized conventions.

Let’s take a look at an example to better illustrate this:

const javaScriptFrameworks = ['Angular', 'React', 'Vue']

const shortUnitFormatter = new Intl.ListFormat('en', {
  style: 'short',
  type: 'unit'
})

const narrowUnitFormatter = new Intl.ListFormat('en', {
  style: 'narrow',
  type: 'unit'
})

const longConjunctionFormatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'conjunction'
})

const longDisjunctionFormatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'disjunction'
})

const longConjunctionItalianFormatter = new Intl.ListFormat('it', {
  style: 'long',
  type: 'conjunction'
})

const longDisjunctionGermanFormatter = new Intl.ListFormat('de', {
  style: 'long',
  type: 'disjunction'
})

shortUnitFormatter.format(javaScriptFrameworks)
// Prints 'Angular, React, Vue'

narrowUnitFormatter.format(javaScriptFrameworks)
// Prints 'Angular React Vue'

longConjunctionFormatter.format(javaScriptFrameworks)
// Prints 'Angular, React, and Vue'

longDisjunctionFormatter.format(javaScriptFrameworks)
// Prints 'Angular, React, or Vue'

longConjunctionItalianFormatter.format(javaScriptFrameworks)
// Prints 'Angular, React e Vue'

longDisjunctionGermanFormatter.format(javaScriptFrameworks)
// Prints 'Angular, React oder Vue'

The first (optional) argument for the ListFormat constructor is the language to be used—'en' for English in the example above. You can also pass an array of these BCP 47 Language Tags.

The second (also optional) parameter is an object with three (also optional) fields:

  • localeMatcher — a string with the value 'lookup' or 'best fit' that specifies which locale matching algorithm to use. The default is 'best fit'.

  • style — a string that sets the separator for the final string. It can take a value of 'long', 'short', or 'narrow'.

  • type — a string that sets the format of the returned string. It can take a value of 'conjunction', 'disjunction', or 'unit'.

dateStyle and timeStyle options for Intl.DateTimeFormat

Intl.DateTimeFormat is a constructor for a language-sensitive date and time formatter, long-supported in the JavaScript ecosystem.

The new options—dateStyle and timeStyle—allow us to control the length of the local-specific formatting of date and time strings.

Let’s see some examples on how to use it when working with time only…

const date = Date.now()

const shortTimeFormatter = new Intl.DateTimeFormat(
  'en',
  { timeStyle: 'short' }
)

const mediumTimeFormatter = new Intl.DateTimeFormat(
  'en',
  { timeStyle: 'medium' }
)

const longTimeFormatter = new Intl.DateTimeFormat(
  'en',
  { timeStyle: 'long' }
)

shortTimeFormatter.format(date)
// Prints '4:46 PM'

mediumTimeFormatter.format(date)
// Prints '4:46:23 PM'

longTimeFormatter.format(date)
// Prints '4:46:23 PM GMT-5'

…and with dates only:

const date = Date.now()

const shortDateFormatter = new Intl.DateTimeFormat(
  'en',
  { dateStyle: 'short' }
)

const mediumDateFormatter = new Intl.DateTimeFormat(
  'en',
  { dateStyle: 'medium' }
)

const longDateFormatter = new Intl.DateTimeFormat(
  'en',
  { dateStyle: 'long' }
)

shortDateFormatter.format(date)
// Prints '4/22/20'

mediumDateFormatter.format(date)
// Prints 'Apr 22, 2020'

longDateFormatter.format(date)
// Prints 'April 22, 2020'

We can also combine the two options to get a date-time string:

const date = Date.now()

const dateTimeFormatter = new Intl.DateTimeFormat(
  'en',
  { dateStyle: 'short', timeStyle: 'long' }
)

const spanishDateTimeFormatter = new Intl.DateTimeFormat(
  'es',
  { dateStyle: 'short', timeStyle: 'long' }
)

const germanDateTimeFormatter = new Intl.DateTimeFormat(
  'de',
  { dateStyle: 'short', timeStyle: 'long' }
)

dateTimeFormatter.format(date)
// Prints 'May 9, 2023 at 11:43 AM'

spanishDateTimeFormatter.format(date)
// Prints '9 de mayo de 2023, 11:43'

germanDateTimeFormatter.format(date)
// Prints '9. Mai 2023 um 11:43'

WeakRefs and FinalizationRegistry objects

According to the WeakRefs TC39 Proposal, this proposal encompasses two major new pieces of functionality:

  1. Creating weak references to objects with the WeakRef class.
  2. Running user-defined finalizers after objects are garbage-collected, with the FinalizationRegistry class.

These interfaces can be used independently or together, depending on the use case.

These new features are advanced features; their correct use takes careful thought, and they are best avoided if possible. To understand how WeakRef works, you first need to understand the concepts of object referencing and garbage collection in JavaScript.

Weak references

WeakRef is an advanced API that provides actual weak references, enabling a window into the lifetime of an object. A weak reference to an object is not enough to keep the object alive, tough. When the only remaining references to a referent (i.e. an object which is referred to by a weak reference) are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed, the weak reference may return the object even if there are no strong references to it.

A primary use for weak references is to implement caches or mappings holding large objects, where it’s desired that a large object is not kept alive solely because it appears in a cache or mapping.

A WeakRef is created with the new WeakRef constructor, and the value of the WeakRef variable can be accessed via the deRef method.

Let’s see a simple example of how to use WeakRef:

const weakRef = new WeakRef({ name: 'Jane' })

console.log(weakRef.deref().name)
// Prints 'Jane'

In this example, WeakRef creates a weak reference to the object passed to it. This means that whenever garbage collection needs to be performed, the JavaScript engine can safely remove the object from memory and free up space since the only reference to that object is from a WeakRef variable. This could be ideal for WebSocket data because of their short lifespans.

Using WeakRef objects and FinalizationRegistry objects together

Finalization is the execution of code to clean up after an object that has become unreachable to program execution. User-defined finalizers enable several new use cases, and can help prevent memory leaks when managing resources that the garbage collector doesn't know about.

WeakRef and finalizers are two features that go together. The FinalizationRegistry method allows you to request a callback after an object has become unreachable (garbage-collected).

First, define a registry with the callback you want to run. Then, call the .register method on the defined registry with the object you want to observe. It will let you know exactly when the memory of the object is freed by the garbage collector.

const registry = new FinalizationRegistry(value => {
  console.log(`'${value}' has been garbage-collected`);
});

registry.register({ object: 'Perform a task' }, 'testObject');

Here, the object passed to the register() method is weakly referenced, so when the value is garbage-collected, the second parameter (testObject) is passed to the finalizer.