DEV Community

Siddharth
Siddharth

Posted on • Updated on

10+ Interesting ECMAScript Proposals

JavaScript is a constantly evolving language, and one of the drivers of new features are Proposals. It's hard to keep up with these proposals, as dozens are submitted to the TC39 committee. Some of them may never be implemented, and some may be a part of your future code.

Because polyfills and transpilers have become popular in recent years, some proposals have gained significant adoption before they’ve even been finalized. Sometimes you can be using proposals which have been rejected via transpilers.

Before getting to the proposals, I'll give you an idea of how they work...

How is a proposal made?

Anyone can submit a proposal to TC39. Each proposal goes through a set of stages till they are added to the spec (stage 4).

  • Stage 0 (Strawman) - The starting point. These proposals can change a lot before they reach the next stage. There are no requirements for this stage – this is just for starting a discussion about the proposal.

  • Stage 1 (Proposal) - This is when a proposal is accepted by TC39, and when the API is thought out and any challenges are outlined. At this stage, a polyfill is made and demos are produced.

  • Stage 2 (Draft) - At this stage, the specification is complete, syntax is described using the formal TC39 spec language. Changes may still happen. If a proposal makes it this far, it will probably be included in the language.

  • Stage 3 (Candidate) - The spec is complete, approved, and JavaScript engines will start implementing the proposal.

  • Stage 4 (Finished) - The proposal has been added to the main JS spec. No more changes will happen. JavaScript engines will ship their implementations.

With that out of the way, we'll start on our list

Proposals

This binding syntax (::)

This proposal introduces a new operator :: which performs this binding and method extraction.

import {map} from 'somelib';

// Old
map.call(arr, mapFn)
// New
arr::map(mapFn)
Enter fullscreen mode Exit fullscreen mode

There are also bound constructors:

class User {
  ...
}

// Old
let users = ['me', 'you'].map(user => new User(user));
// New
let users = ['me', 'you'].map(User::new);
Enter fullscreen mode Exit fullscreen mode

Nested imports

Right now, there is a rule that import statements may only appear at the top of a module. But this proposal aims to relax that restriction.

// Old
import { strictEqual } from "assert";
import { check as checkClient } from "./client.js";
import { check as checkServer } from "./server.js";
import { check as checkBoth } from "./both.js";

describe("fancy feature #5", () => {
  it("should work on the client", () => {
    strictEqual(checkClient(), "client ok");
  });

  it("should work on the client", () => {
    strictEqual(checkServer(), "server ok");
  });

  it("should work on both client and server", () => {
    strictEqual(checkBoth(), "both ok");
  });
});

// New
describe("fancy feature #5", () => {
  import { strictEqual } from "assert";

  it("should work on the client", () => {
    import { check } from "./client.js";
    strictEqual(check(), "client ok");
  });

  it("should work on the server", () => {
    import { check } from "./server.js";
    strictEqual(check(), "server ok");
  });

  it("should work on both client and server", () => {
    import { check } from "./both.js";
    strictEqual(check(), "both ok");
  });
});
Enter fullscreen mode Exit fullscreen mode

Also useful for optimistic imports:

try {
  import esc from "enhanced-super-console";
  console = esc;
} catch (e) {
  // That's ok, we'll just stick to the usual implementations of
  // console.log, .error, .trace, etc., or stub them out.
}
Enter fullscreen mode Exit fullscreen mode

Shorthand improvements

Some improvements to JavaScript shorthands.

// Old
const a = { x: o.x };
const a = { ["x"]: o["x"] };
// New
const a = { o.x };
const a = { o["x"] };

// Old
({ x: a.x } = o);
({ ["x"]: a["x"] } = o);
// New
({ a.x } = o);
({ a["x"] } = o);
Enter fullscreen mode Exit fullscreen mode

as destructuring

When we destructure a nested property, the parent property is not defined. This proposal aims to fix that.

// Old
const {x: {y}} = {x: {y: 1}}
// => x not defined, need to destructure again
const {x} = {x: {y: 1}}

// New
const {x: {y} as x} = {x: {y: 1}}
// => x and y are defined.
Enter fullscreen mode Exit fullscreen mode

Generator arrow functions

Right now, there is no way to make a generator arrow function. This proposal introduces a new generator keyword to define generator functions.

generator function() {}
const foo = async generator function() {};

class Foo {
  x = 1
  generator foo() {}
}
Enter fullscreen mode Exit fullscreen mode

I prefer this, would be a lot cooler:

() =*> something
// Or this
() *=> something
Enter fullscreen mode Exit fullscreen mode

Pipeline operator (|>)

It's syntactic sugar for single argument functions. Basically fn(arg) => arg |> fn.

// Old
let result = exclaim(capitalize(doubleSay("hello")));

// New
let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;
Enter fullscreen mode Exit fullscreen mode

There's also some debate about doing it like this:

const add = (x, y) => x + y;

let result = 1 |> add(%, 10) // Here % is the Left Hand Side (LHS). There are many variations to % (&, #, @, $, () - suggested by me, and more)
Enter fullscreen mode Exit fullscreen mode

Partial Application Operator: ?

Used to partially apply (curry) a function. add(1, ?) returns arg => add(1, arg).

const add = (x, y) => x + y;

// Old
const addOne = add.bind(null, 1);
addOne(2); // 3
const addTen = x => add(x, 10);
addTen(2); // 12

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

const addTen = add(?, 10); // apply from the right
addTen(2); // 12
Enter fullscreen mode Exit fullscreen mode

Object freeze and seal syntax: {# ... #} or {| ... |}

Sugar for Object.freeze and Object.seal:

// Old
let obj = Object.freeze({__proto__: null, things: Object.freeze([1, 2])});
// New
let obj = {# a: [# 1, 2 #] #};

// Old
let obj = Object.seal({__proto__: null, things: Object.seal([1, 2])});
// New
let obj = {| a: [| 1, 2 |] |};

// This would look really nice with Fira Code :D
Enter fullscreen mode Exit fullscreen mode

Block Params

Syntactic sugar for when you pass a callback function.

// Old
fetch(somewhere).then(() => {
  /* handle */
});

// New
fetch(somewhere).then {
  /* handle */
}

// You can also pass arguments to the called function...
_.map(arr) {
  return 1;
}

// ...and to the callback
_.map(arr) do (item) {
  return item + item;
}
Enter fullscreen mode Exit fullscreen mode

.at()

You've probably heard of this one. Relative indexing for Arrays.

const arr = [1, 2, 3];

arr[1] //=> 2
arr.at(1) //=> 2

arr[-1] //=> undefined
arr.at(-1) //=> 3
Enter fullscreen mode Exit fullscreen mode

JSON Modules

Import JSON in a JS file.

import json from "./foo.json";
import json from "./foo.json" assert {type: "json"}
Enter fullscreen mode Exit fullscreen mode

Temporal

This proposal aims to fix Date. I wrote a bit about it here

Temporal.Now.instant()// => ms since unix epoch, similar to Date.now()
Temporal.Now.timeZone() // => system timezone
// more...
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
lukeshiru profile image
LUKESHIRU

One missing proposal in this post that I'm really hyped about is Tuple and Record. It gives us two new types for immutable structures:

// Old
{ foo: 1 } !== { foo: 1 }
["foo", "bar"] !== ["foo", "bar"]

// New
#{ foo: 1 } === #{ foo: 1 }
#["foo", "bar"] === #["foo", "bar"]
Enter fullscreen mode Exit fullscreen mode

And from the ones you listed, Temporal looks promising, and I with the pipe operator is added at some point because it rocks.

Collapse
siddharthshyniben profile image
Siddharth Author

Sounds interesting! I guess I missed that proposal