
Дізнайтесь більше про нові кар'єрні можливості в 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-дій, легко уникнути вкладеності й писати лінійніший код.
Я був здивований декількома речами:
- Функції зворотного виклику для
then
іcatch
можуть повертати будь-яке значення, але вони поводяться по-іншому. Зазвичай, зворотнє значення передається тому виразу, що йде заthen
у ланцюжку. Якщо це значення – обіцянка, далі передається значення, повернене обіцянкою при його виконанні. Це означає, що поверненняPromise.resolve (x)
еквівалентне поверненнюx
. - Функція, передана в конструктор
Promise
, виконується синхронно, але будь-які продовження черезthen
чиcatch
будуть відкладені до наступного циклу подій. Цим пояснюється роботаdefer
. catch
– це зарезервоване ключове слово, використовуване для обробки винятків у JavaScript, але це також ім’я методу, що закріплює обробник помилок в обіцянці. Це схоже на невдалу колізію імен. З іншого боку, полегшує запам’ятовування, тому не все так погано!
Перший пункт здається мені дивним через наступний уявний експеримент: якщо ми дійсно хочемо передати обіцянку наступної функції в ланцюжку? Якщо ми спробуємо зробити це у такий наївний спосіб, обіцянка втратить обгортку, і ми не отримаємо бажаного результату. Ви можете запитати: Навіщо взагалі передавати обіцянку в callback? Можливо, код не знає, якого типу значення він передає у функцію. Можливо, значення створюється десь ще в програмі, і воно просто має бути передане. Треба спершу перевірити, якщо дана обіцянка, і в цьому випадку обернути її в щось ще для захисту на час передачі. Тому я б вважав за краще, щоб функції зворотного виклику завжди повинні були повертати обіцянку (навіть якщо значення обгорнуте в Promise.resolve (value)
).
Обдумавши другий пункт, я дійшов висновку, що функції зворотного виклику повинні бути відкладеними. І ось чому: припустимо, обіцянка не виконана. Вона має бути передана наступному в ланцюжку обробникові помилок, або ж згенерувати виняток за відсутності останнього. Проте йому спершу доведеться дочекатися, поки обробники помилок будуть прикріплені. І як довго чекати? Очевидно, що до наступної ітерації циклу подій.
Незважаючи на ці дивності, promises – приємне доповнення до JavaScript. Пекло callback ‘ів було одним з моїх небажаних аспектів JavaScript, але тепер його можна уникнути.
P.S. Promise
– це монада! Як мінімум, могла б нею бути, коли б не перший пункт.
Оригінал: stephanboyer.com
Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?