ES6 In The Wild

Author: Steve Venzerul

It's A Feature

 

Let/Const

Advantages:

1. Scope makes sense again.

2. Enviro gives you warning when you violate scope rules or assign to const.

3. No more shadowing.

Disadvantages:

None. No really, there aren't any. Some people say let/const is slow in some browsers but that optimization is so premature it's practically going back to 1955.

Destructuring

Advantages:

1. Cut away a lot of the verbosity of initialization, declaration.

2. Purpose and intent is clear and doesn't rely on hacks like `something || something`.

3. Declaration makes it immediately clear what arguments are used by a function, especially when an object is passed in.

Disadvantages:

1. Can easily be abused to obfuscate code when taken too far.

Examples

//Common
const {arg1, arg2} = someObj;
const [val1, val2] = someArray;


//Ok-ish
function dest({prop1: {prop2: prop = 'default'}} = {}) {
  console.log(prop);
}

dest({prop1: {prop2: undefined}});
> default

//-------------------------------------------------------------

//Don't do this, even if it's nicely formatted.
//It's even breaking the parser in this view.
function dest({
    prop1: {prop2: prop}, 
    prop3: p, 
    res = 'val'
  } = {}
) {
  console.log(prop, res);
}

//-------------------------------------------------------------

//***Gotcha***
dest({});

> Cannot read property 'prop2' of undefined

Async/Await

Advantages:

1. Try/Catch/Finally work and appear in the code as intended.

2. Nesting depth caused by promises/callbacks is completely gone.

3. Stack traces no longer suck.

4. Degrades to promises so it's backwards compatible with existing libs.

Disadvantages:

1. Due to compilation with Babel, possibly very serious code bloat.

2. No support for parallelism as with promises. (Easy workaround)

3. Not widely supported in browsers/node yet. (With exceptions)

Examples

//Parallel Gotcha
async function nope() {
    const res1 = await asyncAction1();
    const res2 = await asyncAction2();
    
    return { res1, res2 };
}

//Workaround
async function yep() {
  const [res1, res2] = await Promise.all([asyncAction1(), asyncAction2()]);
  return { res1, res2 };
}


//The good
async function definitely() {
  try {
    res = await asyncAction();
  } catch(error) {
    // Don't forget to rethrow!!
    console.error('oops', error);
    throw error;
  }
}
//THE BLOAT

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

//********Parallel Gotcha*********
var nope = function () {
    var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
        var res1, res2;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        _context.next = 2;
                        return asyncAction1();

                    case 2:
                        res1 = _context.sent;
                        _context.next = 5;
                        return asyncAction2();

                    case 5:
                        res2 = _context.sent;
                        return _context.abrupt("return", { res1: res1, res2: res2 });

                    case 7:
                    case "end":
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function nope() {
        return _ref.apply(this, arguments);
    };
}();

Workaround for bloat? Use bluebird.coroutine(). We noticed that it generates significantly less code than Babel's regenerator runtime.

 

https://babeljs.io/docs/plugins/transform-async-to-module-method/

Modules

Advantages:

1. Finally a standard format for modules instead of various hacks (revealing pattern, constructor, factory function, bare object, etc.)

2. Ability to run static analysis and dependancy graph tooling against code

3. Imports/Exports clearly visible at a glance

4. Circular dependencies work as expected

Disadvantages:

1. Interop with CommonJS/AMD/UMD still not obvious and occasionally presents difficult to debug issues.

2. Transpilation required and likely to remain required for the foreseeable future due to interop issues.

3. Only supported in most bleeding edge versions of browsers at the moment.

Examples

//ModuleA
import modA from './modB'

export default function func() {
  modB.func();
}

//ModuleB
import modA from './modA'

export default function func() {
  modA.some();
}

//---------------------------------------------------------------------

//Gotcha when mixing commonJS and ES6 modules
import comA from './com'

export default function func() {
  comA.some();
}

const modB = require('./modb');
function some() {
  modB.func();
}
//This is the bad part! Do not override the module.exports object.
module.exports = {
  some
}

> comA.some is not a function

Arrow Functions

Advantages:

1. Scope of 'this' variable makes sense and aligns with most other languages. Easier to predict value.

2. Remove the boilerplate, especially when writing short anonymous functions/callbacks.

Disadvantages:

1. When used inside a method of a regular object the result is counter-intuitive.

2. Some of the restrictions can be puzzling if unfamiliar with them. (no arguments array, callee, caller).

Examples

// The good stuff
const arr = [1,2,3,4,5,6];
const mapped = arr.map(x => x * 2);
console.log(mapped);
> [2, 4, 6, 8, 10, 12]


//Sane 'this' value
function str() {
  this.value = 'default';
  
  setTimeout(() => {
    this.value = 'abc';
  });
}

const s = new str();
console.log(s.value);
setTimeout(() => {
  console.log(s.value);
});
//***Plain object gotcha***
const obj = {
  method1: () => { 
    this.method2();
  },
  method2: () => {
    console.log('nope');
  }
}

obj.method1();

> Cannot read property 'method2' of undefined

Object short-hand

Advantages:

1. Concise and clear.

2. Removes boilerplate and repetition.

Disadvantages:

None, use the crap out of it.

//Props and methods
const prop1 = 'blah';
const prop2 = 'super blah';

const obj = { prop1, prop2 };
//Equivalent to:
// const obj = {
//  prop1: prop1,
//  prop2: prop2
//};

const obj2 = {
  method() {
    console.log('method2')
  },
  prop1,
  prop2
};
//Equivalent to: 
//const obj2 = {
//   method: function() {
//     console.log('method2')
//   },
//   prop1: prop1,
//   prop2: prop2
// };

Examples

Class syntax

Advantages:

1. Standard way to write instanciable 'classes'.

2. Cuts away lots of boilerplate in declaring class/static methods.

3. Separates the constructor method from the Class name/declaration.

4. 'super()' is a god send compared to 'Backbone.View.prototype.render.call(this);'

5. Being able to extend native objects (especially Error)

Disadvantages:

1. No way to declare class properties. (Yet)

2. Due to above, you're forced to mix '.prototype' and class syntax in the same file. 

3. Feature encourages thinking in OOP terms instead of understanding the prototypal nature of JS.

class MyClass {
  constructor() {
    // Don't forget the super call!
    this.value = 'blah';
  }
  
  instanceMethod() {
    console.log(this.value);
  }
  
  //How cool is that??
  static method() {
    return this.name;
  }
}

//Properties are annoying, but this can be replaced by declaring
//those in the constructor instead, the same way this.value is
//declared.
MyClass.prototype.instanceProp = 'val1';


const mc = new MyClass();
console.log(mc.instanceProp);
mc.instanceMethod();
console.log(MyClass.method());

> "val1"
> "blah"
> "MyClass"

Examples

Rest/Spread

Advantages:

1. Getting rid of .apply().

2. Cuts away boilerplate.

3. Easy access to remaining arguments without resorting to Array.prototype.slice hack or libs, `_.rest()`.

4. Concatenating arrays without .concat().

Disadvantages:

None, use the crap out of it.

function fn(arg1, ...rest) {
  console.log(arg1, rest);
}

fn('blah', 'more', 'and', 'more', 'to', 'infinity', 'and', 'beyond');
> "blah" ["more", "and", "more", "to", "infinity", "and", "beyond"]


const arr1 = [1,2,3];
const arr2 = [4,5,6];
const together = [...arr1, ...arr2];
console.log(together);
> [1, 2, 3, 4, 5, 6 ]


console.log(Math.max(...arr1));
> 3

Examples

Iterators

Advantages:

1. Ability to construct your own mini-protocols and make iteration semantic on objects that are otherwise cumbersome to iterate without introducing an api on the object itself.

2. Native support using for..of loops (da bomb).

3. Making infinite iterators to generate sequences/ranges.

Disadvantages:

1. The iteration protocol is a little cumbersome with it's `{value: '', done: false}` format.

//A super nifty object iterator
Object.prototype[Symbol.iterator] = function *() {
  for(const key of Object.keys(this)) {
    yield { key, val: this[key] };
  };
}

const obj = {
  prop1: 'val1',
  prop2: 'val2',
  prop3: 'val3'
};

for(const {key, val} of obj) {
  console.log(key, val);
}

> "prop1" "val1"
> "prop2" "val2"
> "prop3" "val3"

Examples

Things we haven't used

But what about generators, proxies, Symbols, template literals, Reflect, unicode, weak map/set, tails calls and all that jazz? 

 

Simple truth is we didn't find a use for them yet, but we sure as hell will use them if the need arises.

There's no need to shoehorn every feature in the language into a code base just because it's been invented.

Go Forth and ES6!

 

(uhem, ES2015, whatever)

ES6 In The Wild

By signupskm

ES6 In The Wild

  • 1,387