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)
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);
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");
});
});
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.
}
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);
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.
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() {}
}
I prefer this, would be a lot cooler:
() =*> something
// Or this
() *=> something
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;
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)
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
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
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;
}
.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
JSON Modules
Import JSON in a JS file.
import json from "./foo.json";
import json from "./foo.json" assert {type: "json"}
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...