JavaScript is anything but a "static" language (pun intended).

It seems everything is changing - even the naming of releases has changed, with years marking the language version.

And starting with the release of ES6 (officially, "ES2015"), the language has has continued to evolve at a rapid pace, introducing a staging system to mark the progress of features and changes.

But which features should you be using now? Or soon?

It's not always obvious, but there is a short list of features from ES2016+ (ES7 and beyond) that I believe every JavaScript developer should be using very soon, if not immediately.

The ES2016+ Short List

My short list of features could stretch all the way back to ES6 - but then it wouldn't be a very short list. 

If you build upon ES6 as the baseline, however, there are only a few features which I believe are real game changes for the every-day JavaScript developer.

  • Object Rest / Spread Properties
  • Observables
  • Async functions

While there are many other great changes that can benefit your work on a day-to-day basis, this short list stands to make an incredible difference in your work.

Object Rest / Spread Properties

How many times have you added underscore.js or lodash to your project, just to get the ".extend" method? I lost count years ago…

This is one of the many things that Object Spread Properties will give you natively in JavaScript. But to understand "spread", first let's look at "rest".

At it's core, Object Rest Properties is an update to destructuring assignment, which allows you to take many values out of object properties and assign them individually to variables, with one line of code:

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

console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

With the changes in Object Rest Properties, you can now get the "rest of the properties" from an object, when doing destructuring assignment, as a new object.

var {a, b, c, ...x} = {a: 1, b: 2, c: 3, x: 4, y: 5, z: 6};

console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

console.log(x); // { x: 4, y: 5, z: 6 }

Destructuring assignment is an important feature in reducing syntax noise and clarifying the intent of code.

But what if you want to "re"-structure multiple objects into a new object? With Object Spread Properties, you can do that easily:

var a = 1, b = 2, c = 3;
var x = {x: 4, y: 5, z: 6};

var obj = {a, b, c, ...x};

console.log(obj); //{a: 1, b: 2, c: 3, x: 4, y: 5, z: 6};

With this, you no longer need underscore.js or lodash to get the "extend" method. You can easily create a shallow copy of an object using Object Spread Params, instead.

var developer = {
  title: "Developer",
  department: "I.T.",
  location: "Building 3, 2nd Floor"
};

var techLeadTitle = {title: "Tech Lead"};

var techLead = {...developer, ...techLeadTitle};

console.log(techLead);
// {
//   title: "Tech Lead",
//   department: "I.T.",
//   location: "Building 3, 2nd Floor"
//};

console.log(developer);
// {
//   title: "Developer",
//   department: "I.T.",
//   location: "Building 3, 2nd Floor"
// };

Just be aware that like underscore and lodash, Object Spread is "last one in wins" - meaning if an object at the end of the list (on the right) has the same property as a previous object in the list, the previous value will be overwritten.

Observables

Have you ever tried to mix native DOM events, jQuery events, events from a framework like Backbone or Ember, and other events from other code?

And when creating event handlers in these frameworks and libraries, have you ever noticed that sometimes your handler fires twice (or more) for the events?

This mismatch of API design and the potential for memory leaks are two of the largest problems that JavaScript developers face when dealing with event based development patterns.

In the past, developers had to be keenly aware of the pitfalls of memory leaks, manually removing the event handlers at the right time. As time moved on, framework developers got smart and started wiring up the magic of unregistering event handlers for you.

But the problem of mismatched API design remains… and can throw some serious wrenches in the code that is supposed to handle the registering and unregistering of event handlers.

Enter observables.

While there are new methods and features added to JavaScript to handle observables natively, the core feature set of an observable - the ability to register and unregister an event handler, among other things - is more an API design, implemented by you (or another JS dev providing support for them).

var resize = new Observable((o) => {

  // listen for window resize and pass height and width
  window.addEventListener("resize", () => {
    var height = window.innerHeight;
    var width = window.innerWidth;
    o.next({height, width});
  });

});

var change = new Observable((o) => {

  // listen for a data model's change event
  // and pass along the key and value that changed
  myModel.on("change", (key, value) => {
    o.next({ key, value });
  });

});

In this example, there are 2 observable objects.

  • The first listens to a browser window being resized through the DOM API directly
  • The second listens to an object's custom event system

While the underlying code for these two events is very different, both of these observable objects share an API that can be used anywhere that supports observables.

This is important for two reasons.

First, the common API for an observable means you can adapt nearly any data source - a custom API, a stream of data, a different event source, and more - into an observable. When you want to use the data source, you no longer have to worry about the API design. It's the same for all observables.

// create an observer to handle notfications
// from various observables
var observer = {
  next: (value) => {
    console.log("VALUE:", value);
  }
};

// listen for data from the observables
resize.observe(observer);
change.observe(observer);

Second, the API standard for an observable includes a method to remove or unregister the events / stream from the underlying code. This means you now have a common way to clean up your event handlers, no matter where the events are coming from.

No more memory leaks due to mismatched API design or forgetting to unregister your handlers (unless you don't implement the method call in your observable, of course).

// an observable to handle window resize events
// --------------------------------------------
var resize = new Observable((o) => {
  
  // function to handle resize event
  // and forward through observable
  function onResize() {
    var height = window.innerHeight;
    var width = window.innerWidth;
    o.next({height, width});
  }

  // listen for window resize and pass height and width
  window.addEventListener("resize", onResize);

  // return a function that will clean up the handler
  return () => {
    window.removeEventListener("resize", onResize);
  };

});

// an observable to handle a data model change
// -------------------------------------------
var change = new Observable((o) => {

  // function to handle model change event
  // and forward through observable
  function onChange (key, value) {
    o.next({ key, value });
  }

  // listen for a data model's change event
  // and pass along the key and value that changed
  myModel.on("change", onChange);

  // return a function that will clean up the handler
  return () => {
    myModel.off("change", onChange);
  };
});

// observer
// --------
// create an observer to handle notfications
// from various observables
var observer = {
  next: (value) => {
    console.log("VALUE:", value);
  }
};

// subscriptions
// -------------
// listen for data from the observables
var resizeSubscription = resize.observe(observer);
var changeSubscription = change.observe(observer);

// clean up
// --------
// remove the observers to prevent memory leaks, etc
resizeSubscription.unsubscribe();
changeSubscription.unsubscribe();

(Note the difference in the Observable implementation in this version, to account for removing the event handlers when "unsubscribe" is called)

There's a lot of power behind observables that can't be shown here, however. For more information on how they work, how they're built, etc, check out this interview and demonstration with Chet Harrison, on functional reactive programming.

Async Functions

Of all the features in ES2016+, async functions are by far the biggest game changer. If you only learn one thing from this article, it should be that you need to start using async functions as soon as possible.

Why, you ask?

If you’ve ever written code like this, you know the pain that is asynchronous workflow in JavaScript:

function createEmployeeWorkflow(cb){

  createEmployee(function(err, employee){
    If (err) { return cb(err); }

    if (employee.needsManager()) {
      
      selectManager(employee, function(err, manager){
        If (err) { return cb(err); }

        employee.manager = manager;
        saveEmployee(employee, function(err){
          If (err) { return cb(err); }

          cb(undefined, employee);
        });
      });
      
    } else {
      
      saveEmployee(employee, function(err){
        If (err) { return cb(err); }
        cb(undefined, employee);
      });
      
    }
  });
}

Nested function after nested function. Multiple redundant, but necessary, checks for errors. It’s enough to make you want to quit… and this is a simple example!

Imagine if your code could look like this, instead:

function createEmployeeWorkflow(cb){
  var err;

  try {
    var employee = createEmployee();
    
    if (employee.needsManager()){
      var manager = selectManager(employee);
      employee.manager = manager;
    }
    
    saveEmployee(employee);
  } catch (ex) {
    err = ex;
  }

  cb(err, employee);
}

Now that looks nice! So much easier to read - as if the code were entirely synchronous.

The good news is that It only takes a few additional keywords to make this work in async functions:

async function createEmployeeWorkflow(cb){
  var err;

  try {
    var employee = await createEmployee();
    
    if (employee.needsManager()){
      var manager = await selectManager(employee);
      employee.manager = manager;
    }
    
    await saveEmployee(employee);
  } catch (ex) {
    err = ex;
  }

  cb(err, employee);
}

By adding “async” to the outer function definition, you can now use the “await” keyword to call your other async functions. The functions that are called with "await" must also be marked as "async", and there's one more key to making them work: promises.

It's common to use callbacks to enable asynchronous code. And I've often said that this is preferable to using promises.

But with async functions, promises are now the way to go.

async function createEmployee(){
  return new Promise((resolve, reject) => {
    
    // do stuff here to create the employee
    var employee = // ...
    
    // now check if it worked or not
    if (/* some success case */) {
      resolve(employee);   
    } else {
      reject(someError);
    }
    
  });
}

By returning a promise from an async function, it can now be consumed with the "await" keyword, as shown above. The end result is code that is far easier to read and understand, easier to maintain, and easier to deal with as a developer.

How To Use These Features, Today.

For the features mentioned here, you can start using them today. Even if the feature definition is not 100% complete, there is enough value and enough support to make it both easy and safe to use.

Object Rest / Spread Properties:

The syntax change behind destructuring and restructuring may help us reduce the amount of code we have to write (or reduce the number of libraries we have to bring along with our code) when assign multiple values to variables, and when making shallow copies of objects. However, there isn't a lot of strange new implementation under the hood. These syntax features are easily handled by Babel.js and other transpilers.

Observables:

Observables are a tool that have been around for a while in functional programming languages, and there are multiple implementations available for JavaScript already. You can find them in RXJS, Babel.js, and TypeScript, along with other non-standard implementations elsewhere.

Async Functions:

The behavior behind this syntax is relatively new, built on top of generators from ES6. Without generators, async functions are very difficult to handle and require third party libraries and extensions for your JavaScript runtime.

However, all modern browsers support generators, making it easy for Babel.js to add async functions, or for you to use the "co" library to create the same feature set without a transpiler.

If you're running Node.js, v4 and beyond support generators and v7.9.5+ supports async functions directly!

3 Rules To Know When It's Safe To Use New JavaScript Features

While the three features above are available and safe to use, the question of when you can use new features, as they are developed, isn't always cut and dry

Prior to ES7 (officially known as "ES2016"), JavaScript moved at a rather slow pace. It would take years for new language features to be standardized, implemented by browsers and other JavaScript runtime environments, and put into general use by developers.

It was sort of easy to know when a feature was ready to use, in the old days, because of this.

Now, though, there's a stage-based introduction of JavaScript features, used by TC-39 - the JavaScript working group. Browser vendors and Node.js tend to move quickly with new features, and it can be hard to keep up with what is and is not usable.

With the new release schedule of the ECMAScript standard, the better browsers auto-updating themselves, and Node.js running to catch up with the V8 JavaScript engine.

How, then, do you know when you can use a new feature?

It's not as difficult as it might seem, honestly.

You can learn what the feature stages are and which ones are safe to use, learn to check the compatibility tables for your JavaScript environment, and learn to know whether or not something is just simple syntax or major behavioral change.

And you can learn all of this through my FREE guide:

3 Rules to know when you can use new JavaScript features

Want to thank me?

If I've helped you out and you want to thank me, why not buy me a coffee?

Derick Bailey

Multiple-time JavaScript conference speaker. Creator of the wildly popular Marionette.js framework. Co-author of Building Backbone Plugins. Trusted by more than 4,000+ developers

Web: watchmecode.net

Comments

Share Top