What’s new in ES2017: Async functions, improved objects and more

Let’s take a look at the most important JavaScript updates that came with ES2017, and also briefly cover how this updating process actually takes place.

The Update Process

JavaScript (ECMAScript) is an ever-evolving standard implemented by many vendors across multiple platforms. ES6 (ECMAScript 2015) was a large release which took six years to finalize. A new annual release process was formulated to streamline the process and rapidly add new features.

The modestly named Technical Committee 39 (TC39) consists of parties including browser vendors who meet to push JavaScript proposals along a strict progression path:

--ADVERTISEMENT--

Stage 0: strawman –
An initial submission of ideas for new or improved ECMAScript features.

Stage 1: proposal –
A formal proposal document championed by at least one member of TC39, which includes API examples, language semantics, algorithms, potential obstacles, polyfills and demonstrations.

Stage 2: draft –
An initial version of the feature specification. Two experimental implementations of the feature are required, although one can be in a transpiler such as Babel.

Stage 3: candidate –
The proposal specification is reviewed and feedback is gathered from vendors.

Stage 4: finished –
The proposal is ready for inclusion in ECMAScript. A feature should only be considered a standard once it reaches this stage. However, it can take longer to ship in browsers and runtimes such as Node.js.

If ES2015 was too large, ES2016 was purposely tiny to prove the standardization process. Two new features were added:

  1. The array .includes() method which returns true or false when a value is contained in an array, and
  2. The a ** b exponentiation operator, which is identical to Math.pow(a, b).

What’s New in ES2017

The feature set for ES2017 (or ES8 in old money) is considered to be the first proper amendment to the ECMAScript specification. It delivers the following goods …

Async functions

Unlike most languages, JavaScript is asynchronous by default. Commands which can take any amount of time do not halt execution. That includes operations such as requesting a URL, reading a file, or updating a database. A callback function must be passed, which executes when the result of that operation is known.

This can lead to callback hell when a series of nested asynchronous functions must be executed in order. For example:

function doSomething() {
  doSomething1((response1) => {
    doSomething2(response1, (response2) => {
      doSomething3(response2, (response3) => {
        // etc...
      };
    });
  });
}

ES2015 (ES6) introduced Promises, which provided a cleaner way to express the same functionality. Once your functions were Promisified, they could be executed using:

function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
}

ES2017 Async functions expand on Promises to make asynchronous calls even clearer:

async function doSomething() {
  const
    response1 = await doSomething1(),
    response2 = await doSomething2(response1),
    response3 = await doSomething3(response2);
}

await effectively makes each call appear as though it’s synchronous while not holding up JavaScript’s single processing thread.

Async functions are supported in all modern browsers (not IE or Opera Mini) and Node.js 7.6+. They’ll change the way you write JavaScript, and a whole article could be dedicated to callbacks, Promises and Async functions. Fortunately, we have one! Refer to Flow Control in Modern JavaScript.

Object.values()

Object.values() is a quick and more declarative way to extract an array of values from name–value pairs within an object. For example:

const myObject = {
  a: 1,
  b: 'Two',
  c: [3,3,3]
}

const values = Object.values(myObject);
// [ 1, 'Two', [3,3,3] ]

You need never write a for … of loop again! Object.values is natively supported in all modern browsers (not IE or Opera Mini) and Node.js 7.0+.

Object.entries()

Object.entries() returns an array from an object containing name–value pairs. Each value in the returned array is a sub-array containing the name (index 0) and value (index 1). For example:

const myObject = {
  a: 1,
  b: 'Two',
  c: [3,3,3]
}

const entries = Object.entries(myObject);
/*
[
  [ 'a', 1 ],
  [ 'b', 'Two' ],
  [ 'c', [3,3,3] ]
]
*/

This provides another way to iterate over the properties of an object. It can also be used to define a Map:

const map = new Map(Object.entries({
  a: 1,
  b: 2,
  c: 3
}));

Object.values is natively supported in most modern browsers (but not IE, Opera Mini and iOS Safari) and Node.js 7.0+.

Object.getOwnPropertyDescriptors()

The Object.getOwnPropertyDescriptors() method returns another object containing all property descriptors (.value, .writable, .get, .set, .configurable, .enumerable).

The properties are directly present on an object and not in the object’s prototype chain. It’s similar to Object.getOwnPropertyDescriptor(object, property) — except all properties are returned, rather than just one. For example:

const myObject = {
  prop1: 'hello',
  prop2: 'world'
};

const descriptors = Object.getOwnPropertyDescriptors(myObject);

console.log(descriptors.prop1.writable); // true
console.log(descriptors.prop2.value);    // 'world'

padStart() and padEnd() String Padding

String padding has been controversial in JavaScript. The popular left-pad library was pulled from npm after it attracted the attention of lawyers representing an instant messaging app with the same name. Unfortunately, it had been used as a dependency in thousands of projects and the internet broke. npm subsequently changed operating procedures and left-pad was un-unpublished.

Native string padding has been added to ES2017, so there’s no need to use a third-party module. .padStart() and .padEnd() add characters to the start or end of a string respectively, until they reach a desired length. Both accept a minimum length and an optional 'fill' string (space is the default) as parameters. Examples:

'abc'.padStart(5);         // '  abc'
'abc'.padStart(5,'-');     // '--abc'
'abc'.padStart(10, '123'); // '1231231abc'
'abc'.padStart(1);         // 'abc'

'abc'.padEnd(5);           // 'abc  '
'abc'.padEnd(5,'-');       // 'abc--'
'abc'.padEnd(10, '123');   // 'abc1231231'
'abc'.padEnd(1);           // 'abc'

.padStart() and .padEnd() are supported in all modern browsers (not IE) and Node.js 8.0+.

Trailing Commas are Permitted

A small ES2017 update: trailing commas no longer raise a syntax error in object definitions, array declarations, function parameter lists, and so on:

// ES2017 is happy!
const a = [1, 2, 3,];

const b = {
  a: 1,
  b: 2,
  c: 3,
};

function c(one,two,three,) {};

Trailing commas are enabled in all browsers and Node.js. However, trailing commas in function parameters are only supported in Chrome 58+ and Firefox 52+ at the time of writing.

SharedArrayBuffer and Atomics

The SharedArrayBuffer object is used to represent a fixed-length raw binary data buffer that can be shared between web workers. The Atomics object provided a predicable way to read from and write to memory locations defined by SharedArrayBuffer.

While both objects were implemented in Chrome and Firefox, it was disabled in January 2018 in response to the Spectre vulnerability.

The full ECMAScript 2017 Language Specification is available at the ECMA International website. Are you hungry for more? The new features in ES2018 have been announced!