Full-Stack Engine

Understanding the new features in JavaScript 2017 (aka ES8)

As you might know, ECMAScript is the language specification for one of the most popular implementations, commonly known as Javascript. It's been receiving revision updates to its features, syntaxis elements, performances enhancements and others, year after year since 2015 with version 6 or ES6. This version is also known as ES2015, the version where promises, arrow functions, and other modern language elements introduced.

The specification version for 2017 is ES8, Standard ECMA-262, commonly known also as ES 2017.

It includes several new features that let you write cleaner code with fewer lines when working with promises, thanks to the addition of async/await, a simplified and elegant way to handle asynchronous operations in code.

It also provides some missing tools to work with strings and arrays, like a way to iterate through the properties of an Object, or dealing with pad spaces in strings. These tasks were provided in the past by polyfills functions, or custom made implementations we kept in some “utils” directory, but now they are instead part of the language and are available for us to use them.

Let's review what the most significant changes made in this revision and what cool features it has to offer are!.

Object.values() and Object.entries()

For a long time, this was a missing feature in the JS programmer's tool-belt. These couple of methods are roughly equivalent for what in other OOP languages like C# or Java is referred as Iterators, in the special case when we work with object properties.

Before ES8, we could access the properties of an object by using Property Accessors, for example obj[‘propA’] or obj.propA to access propA of Object obj, but this requires a prior knowledge of what the obj properties are, this is OK for simple objects, but what happens with objects that contains multiple properties? Moreover, more importantly, what happens if these properties change?. This leads to constant errors injection in our code, as we are not providing code encapsulation.

Instead, using an iterator, we walk through the properties, without any knowledge of what is inside, enforcing code encapsulation in an OOP fashion and providing a convenient way to access all properties in a single statement.

Object.values()

Returns an array of an object's enumerable property values.

As we saw earlier, before ES8, we needed to iterate through object properties using accessors, in some cases breaking code encapsulation and in others, writing several lines, one to access each property, increasing the amount of code needed.

With this new function, we can access every property with a single call, as in the following example.

Example

const obj = { foo: 'bar', abc: 42 };
console.log(Object.values(obj)); // ['bar', 43]

Object.entries()

This method returns an array of an object's own enumerable properties as [key, value] pairs.

Object.entries is similar to Object.values, but this time we have access to both, the key (or name) and the value of every property for a particular object.

Example

const obj = { foo: 'bar', abc: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['abc', 42] ]

Notice that we get an array with sub-arrays in it, as the result of calling this function. Each sub-array has two elements, key and value. If you are interested only in the keys, you might proceed with a map call, like in this example,

const obj = { foo: 'bar', abc: 42 };
console.log(Object.entries(obj).map(([key]) => key)); // ['foo', 'abc']

String.prototype.padEnd()

A useful method when working with strings that we often need in Javascript, before ES8, we had to implement our version or import some polyfills code in our project.

This method adds blank spaces into a string to fill up to a specified length.

str.padEnd(targetLength [, padString])

The improvement here is having a standardized implementation available to use, bug-free and we now are going work the same in the browsers that support it.

Example

const srt = 'qwerty';   
str.padEnd(10)          // "querty    "
str.padEnd(10, "foo");  // "quertyfoof"

String.prototype.padStart()

Similar to padEnd(), this method adds blank spaces to fill the length specified in the parameters for a given string.

str.padStart(targetLength [, padString])

Again, the advantage of using this function, as with padEnd has a native implementation, within the language.

Example

const srt = 'qwerty';
str.padStart(10);         // "    qwerty"
str.padStart(10, "foo");  // "foofqwerty"

Object.getOwnPropertyDescriptors()

A method that returns all own property descriptors, enumerable or not, for a given object. [1]

Object.getOwnPropertyDescriptors(obj)

Where obj is the object to get all own property descriptors.

This method allows an examination of the description of an object's properties, like data descriptors or accessor descriptors (configurable, enumerable, non-enumerable, writable, and others) in the form of string-valued name names and property descriptor. For a detailed explanation of property descriptor take a look at Mozilla developers docs about Object.defineProperty()

Example

Before having this function in ES8, the way to create a shallow copy was using Object.assign. However, this function only swallows behaviors, accessing properties and symbols and not their descriptor, so we miss some possible accessors.

We can now create a shallow copy between two unknown objects by:

Object.create(
  Object.getPrototypeOf(obj), 
  Object.getOwnPropertyDescriptors(obj) 
);

Async Functions

Perhaps the most significant addition in this version: Async Functions introduce a simplified and cleaner way to work with promises.

While the other features explained in the previously came to solve some common problems in the day to day work with JavaScript. Async Functions present a new simplified and cleaner mean to work with Promises and Generators. In others, words are going to change the way we handle asynchronous operations in JavaScript.

Its syntax resembles the way asynchronous operations are works in other OOP languages like async/await in C#.

async function

To understand async in ES7, let's go deeper in seeing what a transpiler like babeljs does when it finds an async function and transpiles into regular ES6 code. For example, let's consider the following trivial async function,

async function add(a, b) {
  return a + b;
}

When babeljs transpiles this into ES6, we get,

let add = (() => {
  var _ref = _asyncToGenerator(function*(a, b) {
    return a + b;
  });

  return function add(_x, _x2) {
    return _ref.apply(this, arguments);
  };
})();

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}

The first statement is an IIFE that returns the definition of function add. This function, in turn, calls another function generated by babel called _asyncToGenerator. As its name suggests, it works as a function generator proxy for the actual generator passed as fn (defined in add's body), and returns a Promise that calls the next() function of fn making use of accessor descriptor as key set to "next" literal.
As you can see, the result of calling "async add" is a promise, which once resolved, works with a proper callback function,

add(1,1).then(v => {
   console.log(v):
})

async function expression

Very similar to async function. The only difference is in this case we don't use function's name to get an anonymous function. async is used to write asynchronous functions as arrow functions or as IIFE.

AsyncFunction

AsyncFunction is an object that represents an async function (as you might know, in JavaScript everything is an object, including functions).
Let's examine what a AsyncFunction looks like again in babeljs transpiler, consider the follow line in ES8 syntax:

var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor

This line transpiles into ES6 as follows,

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}

var AsyncFunction = Object.getPrototypeOf(_asyncToGenerator(function*() {}))
  .constructor;

As we could see in the previous example, async function is translated to a function generator that returns Promises. So, in this case, AsyncFunction is the constructor function that let us obtain this function generator and calls it to get a Promise we can handle when is resolved.
*Note that using AsyncFunction as we incur in the use of Object.getPrototypeOf to get the function generator, adding an extra expensive step.

await

As we could see, async function is, in reality, a function generator hidden from us by the JS engine to make our lives easier. Nevertheless, is vital to understand how it works underneath, so we can understand other syntaxis elements better when we use them in code.
In the case of await, is just a way to yield the function generator execution. Let's consider another example using babeljs. This time suppose we have an add function that takes 2 seconds to complete, let's name it addAfter2Seconds and then we have an async function "add", that awaits for the result of the addition and logs the result,

function addAfter2Seconds(a,b) { 
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(a+b);
    }, 2000);
  });
}

async function add(a,b) {
  var result = await addAfter2Seconds(1,1);
  console.log(result); 
}

Now let's see how this translates in ES6,

let add = (() => {
  var _ref = _asyncToGenerator(function*(a, b) {
    var result = yield addAfter2Seconds(1, 1);
    console.log(result);
  });

  return function add(_x, _x2) {
    return _ref.apply(this, arguments);
  };
})();

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}

function addAfter2Seconds(a, b) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(a + b);
    }, 2000);
  });
}

As you can see, the only thing that changes from previous examples is the yield operator in the function generator, that sets done property in the generator to true until addAfter2Seconds it completes 2 seconds after. (See the function* docs on Mozilla developer's site for more details).

That's all for this overview on the new features of ES 2017. Thank you for reading this blog and please stay tuned for more exciting news and full-stack topics. Take care!.


  1. Taken from ECMAScript Mozilla support docs ↩︎

Author image
Costa Rica
Passionate Software Developer with full-stack development experience.