Знайомство з promises – одним з нововведень ES6


Що таке 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);})

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

Обіцянка підтримує 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

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

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