TECHY360
Everything You Need To Know About Tech

What are ES6 tricks, best practices, and examples?

0 28

Continued cheat sheet for everyday use on ES2015 [ES6] with examples. Share your tips in the comments! 

Maps

Maps are a very useful data structure. Prior to ES6, hash maps were created through objects:

var map = new Object();
map[key1] = 'value1';
map[key2] = 'value2';

However, this does not protect against accidental overloading of functions with specific property names:

> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
> TypeError: Property 'hasOwnProperty' is not a function

These  maps  allow you to set values ​​( set), take them ( get), look for them ( search), and much more.

let map = new Map();
> map.set('name', 'david');
> map.get('name'); // david
> map.has('name'); // true

The great thing is that now we are not required to use only strings. You can use any type as a key, and it will not be cast to a string.

let map = new Map([
    ['name', 'david'],
    [true, 'false'],
    [1, 'one'],
    [{}, 'object'],
    [function () {}, 'function']
]);

for (let key of map.keys()) {
    console.log(typeof key);
    // > string, boolean, number, object, function
}

Note : the use of complex quantities (functions, objects) is not possible when checking for equality when using methods like map.get(). Therefore, use simple quantities: strings, logical variables, and numbers.

You can also iterate through maps through  .entries():

for (let [key, value] of map.entries()) {
    console.log(key, value);
}

Weak map

In versions earlier than ES6, there were several ways to store private data. For example, you could use naming conventions:

class Person {
    constructor(age) {
        this._age = age;
    }

    _incrementAge() {
        this._age += 1;
    }
}

But such agreements can be confusing, and they do not always adhere to them. You can use WeakMaps instead:

let _age = new WeakMap();
class Person {
    constructor(age) {
        _age.set(this, age);
    }

    incrementAge() {
        let age = _age.get(this) + 1;
        _age.set(this, age);
        if (age > 50) {
            console.log('Midlife crisis');
        }
    }
}

The WeakMaps feature is that private data keys do not give property names, which can be seen using  Reflect.ownKeys():

> const person = new Person(50);
> person.incrementAge(); // 'Midlife crisis'
> Reflect.ownKeys(person); // []

A practical example of using WeakMaps is storing data associated with a DOM element, while the DOM itself is not cluttered:

let map = new WeakMap();
let el  = document.getElementById('someElement');

// Store a weak reference to the element with a key
map.set(el, 'reference');

// Access the value of the element
let value = map.get(el); // 'reference'

// Remove the reference
el.parentNode.removeChild(el);
el = null;

// map is empty, since the element is destroyed

As seen above, when an object is destroyed by the garbage collector, WeakMap automatically deletes the key-value pair that was identified by this object.

Promises

The promises that we described in detail in a separate article allow us to turn a “horizontal” code:

func1(function (value1) {
    func2(value1, function (value2) {
        func3(value2, function (value3) {
            func4(value3, function (value4) {
                func5(value4, function (value5) {
                    // Do something with value 5
                });
            });
        });
    });
});

In vertical:

func1(value1)
    .then(func2)
    .then(func3)
    .then(func4)
    .then(func5, value5 => {
        // Do something with value 5
    });

Before ES6, I had to use a  bluebird or the Q. Now Promises are implemented natively:

new Promise((resolve, reject) =>
    reject(new Error('Failed to fulfill Promise')))
        .catch(reason => console.log(reason));

We have two handlers, resolve (the function called when the promise is fulfilled ) and reject (the function called when the promise is not fulfilled ).

Advantages of Promises : error handling with a bunch of nested callbacks is hell. Promises look much nicer. In addition, the meaning of the promise after its resolution is unchanged.

Here is a practical example of using Promises:

var request = require('request');

return new Promise((resolve, reject) => {
  request.get(url, (error, response, body) => {
    if (body) {
        resolve(JSON.parse(body));
      } else {
        resolve({});
      }
  });
});

We can also parallelize  promises to process an array of asynchronous operations using  Promise.all():

Related Posts
1 of 26
let urls = [
  '/api/commits',
  '/api/issues/opened',
  '/api/issues/assigned',
  '/api/issues/completed',
  '/api/issues/comments',
  '/api/pullrequests'
];

let promises = urls.map((url) => {
  return new Promise((resolve, reject) => {
    $.ajax({ url: url })
      .done((data) => {
        resolve(data);
      });
  });
});

Promise.all(promises)
  .then((results) => {
    // Do something with results of all our promises
 });

Generators

Like promises that allow us to avoid the hell of callbacks, generators allow us to smooth out the code, giving the asynchronous code a synchronous look. Generators are functions that can pause their execution and subsequently return the value of an expression.

A simple use case is given below:

function* sillyGenerator() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

var generator = sillyGenerator();
> console.log(generator.next()); // { value: 1, done: false }
> console.log(generator.next()); // { value: 2, done: false }
> console.log(generator.next()); // { value: 3, done: false }
> console.log(generator.next()); // { value: 4, done: false }

next allows you to pass the generator further and evaluate a new expression. The example above is extremely simple, but in fact, generators can be used to write asynchronous code in synchronous form:

// Hiding asynchronousity with Generators

function request(url) {
    getJSON(url, function(response) {
        generator.next(response);
    });
}

And here is a generator function that returns our data:

function* getData() {
    var entry1 = yield request('http://some_api/item1');
    var data1  = JSON.parse(entry1);
    var entry2 = yield request('http://some_api/item2');
    var data2  = JSON.parse(entry2);
}

Thanks to power,  yield we can be sure that there  entry1 will be the necessary data to be transmitted to  data1.

However, you have to come up with something to handle errors. You can use Promises:

function request(url) {
    return new Promise((resolve, reject) => {
        getJSON(url, resolve);
    });
}

And we write a function that will go through the generator using  next, which in turn will use the method  request:

function iterateGenerator(gen) {
    var generator = gen();
    (function iterate(val) {
        var ret = generator.next();
        if(!ret.done) {
            ret.value.then(iterate);
        }
    })();
}

Complementing the promise of our generator, we get an understandable way to transmit errors by  .catch and  reject. At the same time, using the generator is just as simple:

iterateGenerator(function* getData() {
    var entry1 = yield request('http://some_api/item1');
    var data1  = JSON.parse(entry1);
    var entry2 = yield request('http://some_api/item2');
    var data2  = JSON.parse(entry2);
});

Async Await

Although this feature will only appear in ES2016, async await it allows us to do the same as in the previous example, but with less effort:

var request = require('request');

function getJSON(url) {
  return new Promise(function(resolve, reject) {
    request(url, function(error, response, body) {
      resolve(body);
    });
  });
}

async function main() {
  var data = await getJSON();
  console.log(data); // NOT undefined!
}

main();

In fact, this works the same way as generators, but it is better to use this function.

Getters and Setters

ES6 brings support for getters and setters. Here is an example:

class Employee {

    constructor(name) {
        this._name = name;
    }

    get name() {
      if(this._name) {
        return 'Mr. ' + this._name.toUpperCase();  
      } else {
        return undefined;
      }  
    }

    set name(newName) {
      if (newName == this._name) {
        console.log('I already have this name.');
      } else if (newName) {
        this._name = newName;
      } else {
        return false;
      }
    }
}

var emp = new Employee("James Bond");

// uses the get method in the background
if (emp.name) {
  console.log(emp.name);  // Mr. JAMES BOND
}

// uses the setter in the background
emp.name = "Bond 007";
console.log(emp.name);  // Mr. BOND 007

Fresh browsers also allow you to use getters/setters in objects, and then they can be used for calculated properties by adding listeners in front of them:

var person = {
  firstName: 'James',
  lastName: 'Bond',
  get fullName() {
      console.log('Getting FullName');
      return this.firstName + ' ' + this.lastName;
  },
  set fullName (name) {
      console.log('Setting FullName');
      var words = name.toString().split(' ');
      this.firstName = words[0] || '';
      this.lastName = words[1] || '';
  }
}

person.fullName; // James Bond
person.fullName = 'Bond 007';
person.fullName; // Bond 007

Characters

Symbols existed before ES6, but now a public interface is now available for their direct use. Symbols are immutable and unique and can be used as keys of any hash.

Symbol ()

Calling  Symbol() or  Symbol(description) creating a unique character that is not reachable globally. Symbol() It is usually used to add your own logic to third-party objects or namespaces, but you need to be careful with further updates to these libraries. For example, if you want to add a method  refreshComponent to a class  React.Component, make sure that it does not match the method added in the next update:

const refreshComponent = Symbol();

React.Component.prototype[refreshComponent] = () => {
    // do something
}

Symbol.for (key)

Symbol.for(key) will create a symbol that remains immutable and unique, but globally accessible. Two identical calls Symbol.for(key) will return the same Symbol entity.

Note : this is not true for  Symbol(description):

Symbol('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol('foo') // false
Symbol.for('foo') === Symbol.for('foo') // true

Symbols, in particular,  Symbol.for(key)are usually used for interoperability, searching for a character field in the arguments of a third-party object with a known interface, for example:

function reader(obj) {
    const specialRead = Symbol.for('specialRead');
    if (obj[specialRead]) {
        const reader = obj[specialRead]();
        // do something with reader
    } else {
        throw new TypeError('object cannot be read');
    }
}

And in another library:

const specialRead = Symbol.for('specialRead');

class SomeReadableType {
    [specialRead]() {
        const reader = createSomeReaderFrom(this);
        return reader;
    }
}

As an example of using symbols for interoperability, it is worth noting  Symbol.iteratorthat it exists in all iterable types of ES6: arrays, strings, generators, etc. When the method starts, an object with an iterator interface is returned.

Comments
Loading...

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More