ECMAScript 2016+ in Firefox

After the release of ECMAScript 2015, a.k.a. ES6, the ECMAScript Language Specification is evolving rapidly: it’s getting many new features that will help developing web applications, with a new release planned every year.

Last week, Firefox Nightly 54 reached 100% on the Kangax ECMAScript 2016+ compatibility table that currently covers ECMAScript 2016 and the ECMAScript 2017 draft.

Here are some highlights for those spec updates.

Kangax ECMAScript 2016+ compatibility table

ECMAScript 2016

ECMAScript 2016 is the latest stable edition of the ECMAScript specification. ECMAScript 2016 introduces two new features, the Exponentiation Operator and Array.prototype.includes, and also contains various minor changes.

New Features

Exponentiation Operator

Status: Available from Firefox 52 (now Beta, will ship in March 2017).

The exponentiation operator (**) allows infix notation of exponentiation.
It’s a shorter and simpler replacement for Math.pow. The operator is right-associative.

// without Exponentiation Operator
console.log(Math.pow(2, 3));             // 8
console.log(Math.pow(2, Math.pow(3, 2)); // 512

// with Exponentiation Operator
console.log(2 ** 3);                     // 8
console.log(2 ** 3 ** 2);                // 512

Array.prototype.includes

Status: Available from Firefox 43.

Array.prototype.includes is an intuitive way to check the existence of an element in an array, replacing the array.indexOf(element) !== -1 idiom.

let supportedTypes = [
  "text/plain",
  "text/html",
  "text/javascript",
];

// without Array.prototype.includes.
console.log(supportedTypes.indexOf("text/html") !== -1); // true
console.log(supportedTypes.indexOf("image/png") !== -1); // false

// with Array.prototype.includes.
console.log(supportedTypes.includes("text/html")); // true
console.log(supportedTypes.includes("image/png")); // false

Miscellaneous Changes

Generators can’t be constructed

Status: Available from Firefox 43.

Calling generator with new now throws.

function* g() {
}

new g(); // throws

Iterator for yield* can catch throw()

Status: Available from Firefox 27.

When Generator.prototype.throw is called on a generator while it’s executing yield*, the operand of the yield* can catch the exception and return to normal completion.

function* inner() {
  try {
    yield 10;
    yield 11;
  } catch (e) {
    yield 20;
  }
}

function* outer() {
  yield* inner();
}

let g = outer();
console.log(g.next().value);  // 10
console.log(g.throw().value); // 20, instead of throwing

Function with non-simple parameters can’t have “use strict”

Status: Available from Firefox 52 (now Beta, will ship in March 2017).

When a function has non-simple parameters (destructuring parameters, default parameters, or rest parameters), the function can’t have the "use strict" directive in its body.

function assertEq(a, b, message="") {
  "use strict"; // error

  // ...
}

However, functions with non-simple parameters can appear in code that’s already strict mode.

"use strict";
function assertEq(a, b, message="") {
  // ...
}

Nested RestElement in Destructuring

Status: Available from Firefox 47.

Now a rest pattern in destructuring can be an arbitrary pattern, and also be nested.

let [a, ...[b, ...c]] = [1, 2, 3, 4, 5];

console.log(a); // 1
console.log(b); // 2
console.log(c); // [3, 4, 5]

let [x, y, ...{length}] = "abcdef";

console.log(x);      // "a"
console.log(y);      // "b"
console.log(length); // 4

Remove [[Enumerate]]

Status: Removed in Firefox 47.

The enumerate trap of the Proxy handler has been removed.

ECMAScript 2017 draft

ECMAScript 2017 will be the next edition of ECMAScript specification, currently in draft. The ECMAScript 2017 draft introduces several new features, Object.values / Object.entries, Object.getOwnPropertyDescriptors, String padding, trailing commas in function parameter lists and calls, Async Functions, Shared memory and atomics, and also some minor changes.

New Features

Async Functions

Status: Available from Firefox 52 (now Beta, will ship in March 2017).

Async functions help with long promise chains broken up to separate scopes, letting you write the chains just like a synchronous function.

When an async function is called, it returns a Promise that gets resolved when the async function returns. When the async function throws, the promise gets rejected with the thrown value.

An async function can contain an await expression. That receives a promise and returns a resolved value. If the promise gets rejected, it throws the reject reason.

async function makeDinner(candidates) {
  try {
    let itemsInFridge = await fridge.peek();

    let itemsInStore = await store.peek();

    let recipe = await chooseDinner(
      candidates,
      itemsInFridge,
      itemsInStore,
    );

    let [availableItems, missingItems]
      = await fridge.get(recipe.ingredients);

    let boughtItems = await store.buy(missingItems);

    pot.put(availableItems, boughtItems);

    await pot.startBoiling(recipe.temperature);

    do {
      await timer(5 * 60);
    } while(taste(pot).isNotGood());

    await pot.stopBoiling();

    return pot;
  } catch (e) {
    return document.cookie;
  }
}

async function eatDinner() {
  eat(await makeDinner(["Curry", "Fried Rice", "Pizza"]));
}

Shared memory and atomics

Status: Available behind a flag from Firefox 52 (now Beta, will ship in March 2017).

SharedArrayBuffer is an array buffer pointing to data that can be shared between Web Workers. Views on a shared memory can be created with the TypedArray and DataView constructors.

When transferring SharedArrayBuffer between the main thread and a worker, the underlying data is not transferred but instead the information about the data in memory is sent. As a result it reduces the cost of using a worker to process data retrieved on main thread, and also makes it possible to process data in parallel on multiple workers without creating separate data for each.

// main.js
let worker = new Worker("worker.js");

let sab = new SharedArrayBuffer(IMAGE_SIZE);
worker.onmessage = functions(event) {
  let image = createImageFromBitmap(event.data.buffer);
  document.body.appendChild(image);
};
captureImage(sab);
worker.postMessage({buffer: sab})

// worker.js
onmessage = function(event) {
  let sab = event.data.buffer;
  processImage(sab);
  postMessage({buffer: sab});
};

Moreover, a new API called Atomics provides low-level atomic access and synchronization primitives for use with shared memory. Lars T Hansen has already written about this in the Mozilla Hacks post “A Taste of JavaScript’s New Parallel Primitives“.

Object.values / Object.entries

Status: Available from Firefox 47.

Object.values returns an array of a given object’s own enumerable property values, like Object.keys does for property keys, and Object.entries returns an array of [key, value] pairs.

Object.entries is useful to iterate over objects.

createElement("img", {
  width: 320,
  height: 240,
  src: "http://localhost/a.png"
});

// without Object.entries
function createElement(name, attributes) {
  let element = document.createElement(name);

  for (let name in attributes) {
    let value = attributes[name];
    element.setAttribute(name, value);
  }

  return element;
}

// with Object.entries
function createElement(name, attributes) {
  let element = document.createElement(name);

  for (let [name, value] of Object.entries(attributes)) {
    element.setAttribute(name, value);
  }

  return element;
}

When property keys are not used in the loop, Object.values can be used.

Object.getOwnPropertyDescriptors

Status: Available from Firefox 50.

Object.getOwnPropertyDescriptors returns all own property descriptors of a given object.

The return value of Object.getOwnPropertyDescriptors can be passed to Object.create, to create a shallow copy of the object.

function shallowCopy(obj) {
  return Object.create(Object.getPrototypeOf(obj),
                       Object.getOwnPropertyDescriptors(obj));
}

String padding

Status: Available from Firefox 48.

String.prototype.padStart and String.prototype.padEnd add padding to a string, if necessary, to extend it to the given maximum length. The padding characters can be specified by argument.

They can be used to output data in a tabular format, by adding leading spaces (align to end) or trailing spaces (align to start), or to add leading "0" to numbers.

let stock = {
  apple: 105,
  pear: 52,
  orange: 78,
};
for (let [name, count] of Object.entries(stock)) {
  console.log(name.padEnd(10) + ": " + String(count).padStart(5, 0));
  // "apple     : 00105"
  // "pear      : 00052"
  // "orange    : 00078"
}

Trailing commas in function parameter lists and calls

Status: Available from Firefox 52 (now Beta, will ship in March 2017).

Just like array elements and object properties, function parameter list and function call arguments can now have trailing commas, except for the rest parameter.

function addItem(
  name,
  price,
  count = 1,
) {
}

addItem(
  "apple",
  30,
  2,
);

This simplifies generating JavaScript code programatically, i.e. transpiling from other language. Code generator doesn’t have to worry about whether to emit comma or not, while emitting function parameters or function call arguments.

Also this makes it easier to rearrange parameters by copy/paste.

Miscellaneous Changes

Remove proxy OwnPropertyKeys error with duplicate keys

Status: Available from Firefox 51.

The ownKeys trap of a user-defined Proxy handler is now permitted to return duplicate keys for non-extensible object.

let nonExtensibleObject = Object.preventExtensions({ a: 10 });

let x = new Proxy(nonExtensibleObject, {
  ownKeys() {
    return ["a", "a", "a"];
  }
});

Object.getOwnPropertyNames(x); // ["a", "a", "a"]

Case folding for \w, \W, \b, and \B in unicode RegExp

Status: Available from Firefox 54 (now Nightly, will ship in June 2017).

\w, \W, \b, and \B in RegExp with unicode+ignoreCase flags now treat U+017F (LATIN SMALL LETTER LONG S) and U+212A (KELVIN SIGN) as word characters.

console.log(/\w/iu.test("\u017F")); // true
console.log(/\w/iu.test("\u212A")); // true

Remove arguments.caller

Status: Removed in Firefox 53 (now Developer Edition, will ship in April 2017).

The caller property on arguments objects, that threw a TypeError when gotten or set, has been removed.

function f() {
  "use strict";
  arguments.caller; // doesn't throw.
}
f();

What’s Next?

We’re also working on implementing ECMAScript proposals.

New Feature

Function.prototype.toString revision (proposal Stage 3)

Status: Work in progress.

This proposal standardizes the string representation of functions to make it interoperable between browsers.

Lifting Template Literal Restriction (proposal Stage 3)

Status: Available from Firefox 53 (now Developer Edition, will ship in April 2017).

This proposal removes a restriction on escape sequences in Tagged Template Literals.

If an invalid escape sequence is found in a tagged template literal, the template value becomes undefined but the template raw value becomes the raw string.

function f(callSite) {
  console.log(callSite);     // [undefined]
  console.log(callSite.raw); // ["\\f. (\\x. f (x x)) (\\x. f (x x))"]
}

f`\f. (\x. f (x x)) (\x. f (x x))`;

Async Iteration (proposal Stage 3)

Status: Work in progress.

The Async Iteration proposal comes with two new features: async generator and for-await-of syntax.

The async generator is a mixture of a generator function and an async function. It can contain yield, yield*, and await. It returns a generator object that behaves asynchronously by returning promises from next/throw/return methods.

The for-await-of syntax can be used inside an async function and an async generator. It behaves like for-of, but interacts with the async generator and awaits internally on the returned promise.

async function* loadImagesSequentially(urls) {
  for (let url of urls) {
    yield await loadImage(url);
  }
}

async function processImages(urls) {
  let processedImages = [];

  for await (let image of loadImagesSequentially(urls)) {
    let processedImage = await processImageInWorker(image);
    processedImages.push(processedImage);
  }

  return processedImage;
}

Conclusion

100% on the ES2016+ compatibility table is an important milestone to achieve, but the ECMAScript language will continue evolving. We’ll keep working on implementing new features and fixing existing ones to make them standards-compliant and improve their performance. If you find any bug, performance issue, or compatibility fault, please let us know by filing a bug in Bugzilla. Firefox’s JavaScript engine engineers can also be found in #jsapi on irc.mozilla.org. 🙂

4 responses

Post a comment

  1. fanboynz wrote on :

    Whats the status of the Tail Calls (to pass the es6 test) ?

    Reply

    1. Hannes Verschore wrote on :

      We have no ETA at this time, sorry.

      Reply

  2. Adam wrote on :

    You should just use python in Mozilla…

    Reply

  3. mathieu wrote on :

    Very nice summary!

    Thanks for taking the time to write this!

    Nit: you could also mention Object Rest Spread https://bugzilla.mozilla.org/show_bug.cgi?id=1339395

    Reply

Post Your Comment