Jan 6 Adapting Your Code to ESNext Patterns

There is nothing quite like the start of a new year to really dive into new and exciting information; just as time presses on around us, Javascript has pressed on, constantly latching onto a horde of devices, and for some it is hard to really keep up. ES2017 is the latest and greatest edition of the ECMAScript specification – the checklist which determines what Javascript language features browsers should support in their respective engines – and builds on the strong foundations that ES2016 and ES2015 laid out.

With ES2017 and the power of transpilers such as Babel and Typescript, modern Javascript can be written with the most recent features, transformed, and then shipped as backwards-compatible code. This is invaluable as it allows code to be "future-proof", so-to-speak. This is great for new applications, side projects, and open source tools that developers will be or are currently building, but what about that legacy code in that corner of the computer that rarely sees daylight?

That is where this guide comes in; the following sections will outline some new features from ES2015-ES2017, and detail how to adapt some familiar patterns currently used in frontend Javascript development to modern patterns.

Async/Await

Do Javascript developers dream of asynchronous code? Perhaps. Regardless, none need dream anymore, for the async and await keywords have arrived in ES2017. These work in tandem to provide asynchronous behavior to functions. (Including class methods!)

async keyword

Asynchronous functions are denoted by placing the async keyword before the function expression. This includes IIFE functions and class methods.

Using this keyword will transform the return value of the function into a Promise, with any value returned assigned to the PromiseValue.

await keyword

Inside an asynchronous function, use the await keyword to halt all other Javascript execution until the await-ed expression is complete. If this expression is Promise-based, the assigned variable(s) assume the value of the fulfilled Promise, otherwise the variable(s) assume the value of the expression as a resolved Promise.

Adapting to Async/Await

To adapt any function to be used in the async/await pattern, there are two approaches one can take - 1. Returning a Promise or 2. Returning an implicit Promise.resolve call. For demonstrating these two approaches, consider the following functions:

Promise Approach

This approach may be more suited to more complex functions, such as one that does have actual asynchronous functionality. It is useful because the function body executes inside a returned Promise body, using the resolve function to return the result of the function body, and using the reject function to return a fallback value or error message if the function body throws an error.

This allows more refined error-handling for functions that may need it.

Implicit Approach

Using the implicit approach, the function’s returning value is implicitly transformed using a Promise.resolve call into a resolved Promise, with the function's result as the PromiseValue. If desired, the value could be explicitly wrapped in a call to the resolve method to increase readability.

Classes

Until ES2015, Javascript has not had an implementation of a class system, which is not surprising, seeing as the language is written to have prototype-based inheritance. However, this changed when the class structure was introduced. While technically syntactic-sugar, Javascript classes allow cleaner and safer extendability of the function-based "classes" that existed before ES2015.

Classes have become very popular with several prominent Javascript frameworks and libraries including Angular and React.

The following code demonstrates how an ES2015 class is structured and instantiated:

Constructor

The class constructor is a function that describes the properties and executes any code upon creation of the class instance. The function can take arguments, allowing instances to assign unique values to class properties.

Note that any property declared here will be accessible from the class' instance.

Methods

Methods are functions that are accessible from the class instance. Often, methods will interact with or access class properties. To do this, call the property using this.propertyName, and methods will have access to their instance's value of the class property.

Adapting to Classes

To adapt a pre-ES2015 function-based "class" implementation to use the class structure, move all initialized properties into a constructor method, and move all methods out to their own expression definitions. Consider the following function-based class:

By wrapping the code in a class definition, moving the initialized properties and their assigning parameters into the constructor, and extracting methods to the class' top level, the function "class" is successfully migrated to the modern class structure.

Default Arguments

Problems arise when functions that need parameters are not provided the appropriate amount of arguments. In the past, ternary operators or a combination of the AND/OR operators helped developers fill in the gaps when some arguments were left out, but were still necessary for the function to successfully execute. Consider the following functions:

Using ternary or the AND/OR operators works fine for string parameters. However, if the function parameters expect false, 0, or another "falsy" value, these operators will read the value and use the fallback, even though the fallbacks are meant to be used only if a value is not present.

Adapting to Default Arguments

To remedy the faults of this pattern, use the new default arguments feature from ES2015; instead of defining a fallback value in the function body, assign the default argument value inside the argument list using the = operator.

Destructuring

When working with objects and arrays, sometimes only one or two values are needed. Pre-ES2015, this would have meant selecting the individual properties (for objects) or elements (for arrays) from the respective variable.

Adapting to Destructuring

With ES2015's destructuring feature, individual properties/elements can be extracted directly from the object/array variable itself.

To do this, destructured values are declared using a variable assignment expression: use a variable declaration keyword (const, let, or var) and wrap the desired properties/elements to be extracted on the left hand side of the assignment with the respective structure delimiters ({} for objects, [] for arrays). On the right hand side should be a variable that holds elements/properties to be destructured.

With objects, properties extracted using destructuring are identified by their keys; with arrays, elements are extracted using their position in the array, and are identified by variable identifiers, as if they are standard variable declarations.

Object and array destructuring can even be combined with default arguments to setup fallback values for properties/elements that may not be present in the object/array; this is useful when using right-hand expressions like data fetching or other potentially "unpredictable" values.

Wrapping Up

The arrival of ES2015 heralded a refreshing renaissance of features and exciting developments in Javascript tooling. With the latest release of ES2017, a number of features are already available to replace and bulletproof old patterns whilst yielding cleaner and forward-thinking code.