Ознайомлення з promises – одним з нововведень ES6


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

Що таке promise?

Взагалі кажучи, promises (дослівно – “обіцянки”) – це обгортки для функцій зворотного виклику (callback). Їх можна використати для впорядковування синхронних і асинхронних дій.

За допомогою promises Ви зможете переписати:

$.ajax ({ url: '/value', success: function (value) { var result = parseInt (value)  + 10; setTimeout (function (){ console.log (result); }, 5000); }});

у такому вигляді:

ajax ({ url: '/value'}).then (function (value){
return parseInt (value)  + 10;}).then ( delay (5000)).then (function (result) { console.log (result);})

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

Обіцянка підтримує 2 методи: then і catch. then приймає продовження, що є функцією зворотного виклику, яка приймає результат як аргумент і повертає нову обіцянку або інше значення. Аналогічно, catch – це callback, що викликається при виникненні винятку або іншої помилки.

Повна версія then має обидві поведінки:

promise.then (onFullfilled, onRejected)

Так, catch можна визначити:

promise.catch (onRejected)  := promise.then (null, onRejected)

Як створити обіцянку?

Для створення обіцянки Вам не потрібно створювати об’єкт з методами then і catch. Замість цього можна використати конструктор Promise:

var promise = new Promise (function (resolve, reject) { // maybe do some async stuff in here resolve ('result');});

Достатньо викликати resolve, коли обіцянка виконана, або викликати reject, якщо щось пішло не так. Також можна згенерувати виняток. Якщо Ви хочете обернути значення на обіцянку, яку буде виконано негайно, можна просто написати Promise.resolve (value). У зворотному випадку достатньо написати Promise.reject (error).

Таймаути

Функція setTimeout використовується для виконання коду після певної затримки. Ось її версія, реалізована за допомогою promises:

function delay (milliseconds) { return function (result) { return new Promise (function (resolve, reject) { setTimeout (function (){ resolve (result); }, milliseconds); }); };}

Приклад використання:

delay (1000) ('hello').then (function (result) { console.log (result);});

Ви можете здивуватися, що delay карирована. Це зроблено для того, щоб її можна було включити в послідовність після іншої обіцянки:

somePromise.then (delay (1000)).then (function (){ console.log ('1 second after somePromise!');});

AJAX

AJAX – класичний приклад використання promises. Якщо Ви використовуєте jQuery, то побачите, що $.ajax не повністю сумісна з promises, бо не підтримує catch. Проте ми з легкістю можемо створити обгортку:

function ajax (options) { return new Promise (function (resolve, reject) { $.ajax (options).done (resolve).fail (reject); });}

Приклад:

ajax ({ url: '/'}).then (function (result) { console.log (result);});

Чи, якщо Ви не користуєтеся jQuery, то можете створити обгортку XMLHttpRequest:

function ajax (url, method, data) { return new Promise (function (resolve, reject){ var request = new XMLHttpRequest ();
request.responseType = 'text'; request.onreadystatechange = function (){ if (request.readyState === XMLHttpRequest.DONE) { if (request.status === 200) { resolve (request.responseText); } else { reject (Error (request.statusText)); } } }; request.onerror = function (){ reject (Error ("Network Error")); }; request.open (method, url, true); request.send (data); });}

Приклад:

ajax ('/', 'GET').then (function (result) { console.log (result);});

Відкладене виконання

Запитання: У якому порядку будуть виведені рядки після виконання цього коду?

new Promise (function (resolve, reject) { console.log ('A'); resolve ();}).then (function (){ console.log ('B');
});console.log ('C');

Відповідь: A, C, потім B.

Дивно, правда? thenперехідник відкладений, а переданий в конструктор Promise – ні. Ми можемо скористатися такою поведінкою then. Часто хочеться відкласти виконання деякої частини коду до завершення асинхронної частини. Раніше для цього можна було використати setTimeout (func, 1), але тепер для цього можна написати обіцянку:

var defer = Promise.resolve ();

Його можна використати так:

defer.then (function (){ console.log ('A');});
console.log ('B');

Цей код виведе B, потім A. Хоча це і не коротше, ніж setTimeout (func, 1), але роз’яснює наші наміри і сумісне з іншими обіцянками. Таким чином, ми отримуємо більше структурований код.

Прикінцеві зауваження

Цінність обіцянок виявляється, коли весь асинхронний код структурований за їх допомогою. Використовуючи компоновані обіцянки для таймерів або AJAX-дій, легко уникнути вкладеності й писати лінійніший код.

Я був здивований декількома речами:

  1. Функції зворотного виклику для then і catch можуть повертати будь-яке значення, але вони поводяться по-іншому. Зазвичай, зворотнє значення передається тому виразу, що йде за then у ланцюжку. Якщо це значення – обіцянка, далі передається значення, повернене обіцянкою при його виконанні. Це означає, що повернення Promise.resolve (x) еквівалентне поверненню x.
  2. Функція, передана в конструктор Promise, виконується синхронно, але будь-які продовження через then чи catch будуть відкладені до наступного циклу подій. Цим пояснюється робота defer.
  3. catch – це зарезервоване ключове слово, використовуване для обробки винятків у JavaScript, але це також ім’я методу, що закріплює обробник помилок в обіцянці. Це схоже на невдалу колізію імен. З іншого боку, полегшує запам’ятовування, тому не все так погано!

Перший пункт здається мені дивним через наступний уявний експеримент: якщо ми дійсно хочемо передати обіцянку наступної функції в ланцюжку? Якщо ми спробуємо зробити це у такий наївний спосіб, обіцянка втратить обгортку, і ми не отримаємо бажаного результату. Ви можете запитати: Навіщо взагалі передавати обіцянку в callback? Можливо, код не знає, якого типу значення він передає у функцію. Можливо, значення створюється десь ще в програмі, і воно просто має бути передане. Треба спершу перевірити, якщо дана обіцянка, і в цьому випадку обернути її в щось ще для захисту на час передачі. Тому я б вважав за краще, щоб функції зворотного виклику завжди повинні були повертати обіцянку (навіть якщо значення обгорнуте в Promise.resolve (value)).

Обдумавши другий пункт, я дійшов висновку, що функції зворотного виклику повинні бути відкладеними. І ось чому: припустимо, обіцянка не виконана. Вона має бути передана наступному в ланцюжку обробникові помилок, або ж згенерувати виняток за відсутності останнього. Проте йому спершу доведеться дочекатися, поки обробники помилок будуть прикріплені. І як довго чекати? Очевидно, що до наступної ітерації циклу подій.

Незважаючи на ці дивності, promises – приємне доповнення до JavaScript. Пекло callback ‘ів було одним з моїх небажаних аспектів JavaScript, але тепер його можна уникнути.

P.S. Promise – це монада! Як мінімум, могла б нею бути, коли б не перший пункт.

Оригінал: stephanboyer.com

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


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

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