What Future Holds For JavaScript — Upcoming Features And Proposals

Every new JavaScript feature goes through four stages until it gets officially included in the language. A proposal starts at Stage 0, and chances are it will never get out of this stage. At Stage 3 though the proposal has almost 100% chance getting into the next version of the language. By that time there are usually already babel transformation plugins presented, and some browser engines might already have it implemented.

Now let's have a look at some of the most anticipated proposals that have high chances to get into the language.

BigInt (Stage 3)

BigInt is a new primitive type that allows to have integers and perform math operations on numbers higher than Number.MAX_SAFE_INTEGER (which is 9007199254740991).

The proposed syntax goes like this:

const x = 9007199254740991n;

const y = BitInt("9007199254740991234");

Note that little n at the end of the number. In the second example, we use a constuctor to build a BigInt from a string, cause building from a regular number for such a big one wouldn't work.

It also can be compared to the regular numbers, and it is not equal strictly but loosely it is:

0n === 0; // false
0n == 0; // true

Though the biggest gotcha here is that you can't mix in the regular numbers with BitInts performing Math operations:

1n + 2; // TypeError!

Private methods (Stage 3)

I believe some of the features are implemented only to make JavaScript look more familiar to devs working in big corporations (that is Java and C# programmers). Thus all the changes around classes started from EC6 (just reminder, there were no "classes" prior to that in JavaScript).

Of cause, EC6 classes are still missing some of the features, one of which is private members.

Not a fan, but I guess this is where it was heading.

Soon we'll be able to add private methods and members to classes. They will be accessible when called from the other methods but won't be visible from the outside. Thus it lets us hide the implementational details of the class.

Here's an example:

class Counter extends HTMLElement {
  #xValue = 0;

  get #x() { return #xValue; }
  set #x(value) {
    this.#xValue = value;
    window.requestAnimationFrame(this.#render.bind(this));
  }

  #clicked() {
    this.#x++;
  }

  constructor() {
    super();
    this.onclick = this.#clicked.bind(this);
  }

  connectedCallback() { this.#render(); }

  #render() {
    this.textContent = this.#x.toString();
  }
}

Note that little # symbol. This is a marker for a private field or method.

Another proposal close to this one is Static class features (also at Stage 3), which allows for adding static fields into classes and thus making JavaScript classes complete clones of Java classes (syntactically at least)

Nullish Coalescing for JavaScript (Stage 3)

This one should be particularly handy. The way falsy notion works in JavaScript is that all values are considered either truthy or falsy. That is often used to set a default value for a variable. For example,

function greet(argName) {
  const name = argName || "stranger"
  console.log(`Welcome, ${name}`)
}

However, 0, '' (the empty string), and false are also "falsy". Which depending on the context might be proper values that should not be replaced with the defaults.

So what we really need here is some operator that will ensure the presense of the value (thus, not null and not undefined). Meet the new ?? operator:

const weight = 0 || 10; // => 10
const weight = 0 ?? 10; // => 0

Such an operator is very welcomed as it will probably help to reduce and prevent many errors.

Optional Chaining (Stage 3)

When you need to dif up a value from a nested object, you do something like this:

const age = deparatments[0].employees[0].personal.age;

However, if you're not sure about your data, you might get null or undefined at some point in this chain so it will break with an exception. One way to make it safer might be to insert the default values:

const age = (
  ((((deparatments || [])[0] || {}).employees || [])[0] || {}).personal || {}
).age;

This is not too readable though. As a better alternative, we might use a third party library (say, lodash):

const age = _.get(deparatments, "[0].employees[0].personal.age");

With the new optional chaining operator ?., we can access the fields safely:

const age = deparatments?.[0]?.employees?.[0]?.personal?.age;

This code will not throw an exception but return null instead.

Just a quick note here, as I already see how this can be overused. Long chains like this can easily lead to bugs which are hard to debug.

Don't use it when the data structure is defined and should not contain nulls/undefined. For example, if there is an error in the backend code in the API (or the data is corrupted in the databse), you actually want it fail with the exception as soon as possible so you know that something is wrong.

Decorator (Stage 2)

You might have already used decorators. (those little markers starting with @). They are used quite intensively in Angular and are supported in TypeScript or through a babel plugin. So it's a rather surprise it is still only at stage 2.

The core idea of decorators is a possibility to alter the functionality of some code by wrapping it up into another function.

For example,

import { @logged } from "./logged";

class C {
  @logged
  method(arg) {
    this.#x = arg;
  }

  @logged
  set #x(value) { }
}

In this case the method doesn't know anything about logging, but by wrapping it or marking it with @logged annotation we can "inject" the logging capability right into it.

Here's an implementation of it:

export decorator @logged {
  @wrap(f => {
    const name = f.name;
    function wrapped(...args) {
      console.log(`starting ${name} with arguments ${args.join(", ")}`);
      f.call(this, ...args);
      console.log(`ending ${name}`);
    }
    wrapped.name = name;
    return wrapped;
  })
}

Basically, here we simply get the initial function, then we wrap it into a new function surrounding the initial code with some additional functionality (in this case, we simply add console.log).

Decorators are quite famous in an object-oriented language (though sometimes they are called annotations). This particular implementation is very akin to Python one.

Other cool proposals out there

let budget = 1_000_000_000; // 1 billion

This one allows having weak references to objects (when you have a weak reference only garbage collector may still destroy the object). It also adds finalizers (methods that are executed when the object is being destroyed).

A rather curious proposal allowing to hide function implementation by adding a special directive:

function foo() {
  const x = () => {};

  const y = () => {
    "hide implementation";
    class Z {
      m() {}
    }
  };
}

Quite anticipated but still at the early Stage 1. I wrote a full article on this feature here.

Partial application is something many functional programming languages have. Also, one can achieve the same thing with a 3rd party library like Ramda.js. Here though it becomes a part of language syntax:

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

const addTen = add(?, 10); // apply from the right
addTen(2); // 12
  • Promise.any Promise.any returns a promise that is fulfilled by the first given promise, or rejected with an array of rejection reasons if all of the given promises are rejected.

Where to go next?

We've only scratched the surface here. If you want to read more about coming features, the best place to start is the official page of the tc39 commitee.

Also, if you'd like to use some of the features today, there are babel transformation plugins available for most of them. Just google babel + the name of the feature.

🔥 100+ questions with answers
🔥 50+ exercises with solutions
🔥 ECMAScript 2023
🔥 PDF & ePUB