Що і як в ES6: хитрощі, кращі практики і приклади. Частина друга. Мепи, слабкі мепи, обіцянки, генератори, async / await, гетери / сетери, символи


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Продовження шпаргалки для повсякденного використання по ES2015 [ES6] з прикладами. Діліться своїми порадами в коментарях!

Мепи

Мепи – це дуже корисна структура даних. До ES6 хеш-мепи створювалися через об’єкти:

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

Проте це не захищає від випадкового перевантаження функцій з конкретними іменами властивостей:

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

Справжні мепи дозволяють установлювати значення (set), брати їх (get), шукати (search) і багато чого іншого.

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

Найчудовіше – це те, що тепер ми не зобов’язані використовувати тільки рядки. Можна використати будь-який тип як ключ, і він не буде приведений до рядкового виду.

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
}

Примітка. Використання складних величин (функцій, об’єктів) неможливе при перевірці на рівність за використання методів на кшталт map.get (). Тому використовуйте прості величини: рядки, логічні змінні й числа.

Також за мепами можна ітерируватися через .entries ():

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

Слабкі мепи

У версіях молодше ES6 було декілька способів зберігання приватних даних. Наприклад, можна було використати угоди з іменування:

class Person { constructor (age) { this._ age = age; }_incrementAge (){ this._ age += 1; }}

Проте такі угоди можуть заплутати, та й не завжди їх дотримуються. Замість цього можна використати WeakMaps:

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'); } }}

Фішкою WeakMaps є те, що ключі приватних даних не видають імена властивостей, які можна побачити, використовуючи Reflect.ownKeys ():

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

Практичним прикладом використання WeakMaps є зберігання даних, пов’язаних з елементом DOM, при цьому сама DOM не захаращується:

let map = new WeakMap ();let el = document.getElementById ('someElement');// Store a weak reference to the element with a keymap.set (el, 'reference');// Access the value of the elementlet value = map.get (el); // 'reference'// Remove the referenceel.parentNode.removeChild (el);el = null;// map is empty, since the element is destroyed

Як видно вище, коли об’єкт знищується збирачем сміття, WeakMap автоматично видаляє пару ключ-значення, яка ідентифікувалася цим об’єктом.

Обіцянки

Обіцянки, про які ми детально розповідали в окремій статті, дозволяють перетворити “горизонтальний” код:

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

на вертикальний:

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

До ES6, доводилося використати bluebird чи Q. Тепер Promises реалізовані нативно:

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

У нас є два обробники: resolve (функція, що викликається при виконанні обіцянки) і reject (функція, що викликається при невиконанні обіцянки).

Переваги Promises: обробка помилок із купою вкладених колбеків – це пекло. Обіцянки ж виглядають набагато приємніше. Крім того, значення обіцянки після його дозволу незмінне.

Ось практичний приклад використання Promises:

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

Ми також можемо розпаралелювати обіцянки для обробки масиву асинхронних операцій, використовуючи Promise.all ():

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 });

Генератори

Подібно до обіцянок, що дозволяють нам уникнути пекла колбеків, генератори дозволяють згладити код, надаючи асинхронному коду синхронний вигляд. Генератори – це функції, які можуть призупинити своє виконання і згодом повернути значення виразу.

Простий приклад використання наведений нижче:

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 дозволяє передати генератор далі й обчислити новий вираз. Приклад, наведений вище, простий, але насправді генератори можна використати для написання асинхронного коду в синхронному виді:

// Hiding asynchronousity with Generatorsfunction request (url) { getJSON (url, function (response) { generator.next (response); });}

Ось функція-генератор, що повертає наші дані:

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);}

Завдяки силі yield ми можемо бути впевненими, що в entry1 будуть потрібні дані, передані в data1.

Проте для обробки помилок доведеться щось вигадати. Можна використати Promises:

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

І ми пишемо функцію, яка проходитиме по генератору, використовуючи next, який, у свою чергу, використовуватиме метод request:

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

Доповнюючи обіцянкою наш генератор, ми отримуємо зрозумілий спосіб передачі помилок шляхом .catch і reject. При цьому використати генератор так само просто:

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

Хоча ця функція з’явиться тільки в ES2016, async await дозволяє нам робити те саме, що і в попередньому прикладі, але з меншими зусиллями:

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 ();

По суті, це працює так само, як і генератори, але використати краще саме цю функцію.

Гетери і сетери

ES6 привнесла підтримку гетерів і сетерів. Ось приклад:

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 backgroundif (emp.name) { console.log (emp.name); // Mr. JAMES BOND}// uses the setter in the backgroundemp.name = "Bond 007";console.log (emp.name); // Mr. BOND 007

Свіжі браузери також дозволяють використовувати гетери / сетери в об’єктах, і тоді їх можна використати для обчислених властивостей, додаючи слухачів перед ними:

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 Bondperson.fullName = 'Bond 007';person.fullName; // Bond 007

Символи

Символи були і до ES6, але тепер став доступний публічний інтерфейс для їх прямого використання. Символи незмінні й унікальні, їх можна використати як ключі будь-якого хеша.

Symbol ( )

Виклик Symbol () чи Symbol (description) створить унікальний символ, недоступний глобально. Symbol () зазвичай використовується для додавання своєї логіки в сторонні об’єкти або простори імен, але треба бути обережним з подальшими оновленнями цих бібліотек. Наприклад, якщо Ви хочете додати метод refreshComponent у клас React.Component, переконайтеся, що він не збігається з методом, доданим у наступному оновленні:

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

Symbol.for (key)

Symbol.for (key) створить символ, який як і раніше буде незмінним і унікальним, але доступним глобально. Два ідентичні виклики Symbol.for (key) повернуть ту саму сутність Symbol.

Примітка. Це неправильно для Symbol (description):

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

Символи, зокрема, Symbol.for (key), зазвичай використовують для інтероперабельності, здійснюючи пошук символьного поля в аргументах стороннього об’єкта з відомим інтерфейсом, наприклад:

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'); }}

А в іншій бібліотеці:

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

Як приклад використання символів для інтероперабельності варто назвати Symbol.iterator, наявний в усіх ітерированих типах ES6: масивах, рядках, генераторах і т. д. При запуску методу повертається об’єкт з інтерфейсом ітератора.


До речі, ознайомитися з прикладами використання всіх нових можливостей можна, пройшовши навчальний відеокурс з JavaScript, про який ми писали раніше.

За матеріалами es6-cheatsheet

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *