JavaScript Promise

19 серпня 2016 12:45 comandante 658 5

JavaScript Promise - результат операції, яка ще не завершена, але буде в якийсь невизначений момент в майбутньому. Прикладом такої операції є мережевий запит. Коли ми витягуємо дані з деякого джерела, наприклад з API, для нас немає ніякого способу абсолютно визначити, коли буде отримано відповідь.

Це може бути проблематичним, якщо у нас є інші операції, що залежать від завершення цього мережевого запиту. Без Promises, ми повинні використовувати зворотні виклики для визначення дій, які повинні відбутися в певній послідовності. Це не обов'язково стане проблемою, якщо ми маємо тільки одну асинхронну дію. Але якщо нам потрібно завершити декілька асинхронних дій послідовно, зворотні виклики стають некерованими і призведуть до сумнозвісного callback hell.

doSomething(function(responseOne) {
doSomethingElse(responseOne, function(responseTwo, err) {
if (err) { handleError(err); }
doMoreStuff(responseTwo, function(responseThree, err) {
if (err) { handleAnotherError(err); }
doFinalThing(responseThree, function(err) {
if (err) { handleAnotherError(err); }
// Complete
}); // end doFinalThing
}); // end doMoreStuff
}); // end doSomethingElse
}); // end doSomething 

Promises забезпечують стандартизований і чистий метод визначення задач, які потрібно реалізувати в певній послідовності.

doSomething()
.then(doSomethingElse)
.catch(handleError)
.then(doMoreStuff)
.then(doFinalThing)
.catch(handleAnotherError) 

Створення Promises

Promise створюється за допомогою Promise конструктору. Приймається функція з двома аргументи (resolve, reject) в якості єдиного параметра.

var promise = new Promise( function(resolve, reject) { /* Promise content */ } 

enter image description here

В середині функції ми можемо виконати будь-які асинхронні задачі, які ми хочемо. Для позначення Promise як виконана, ми викликаємо resolve(), необов’язково передаючи значення яке ми хочемо повернути. Для позначення Promise як відхилена або провалена, ми викликаємо reject(), при цьому необов’язково передаючи повідомлення про помилку. Перед тим, як Promise виконана або відхилена, вона знаходиться в стані очікування.

Ось загальна Promise-ified версія XMLHttpRequest:

/* CREDIT - Jake Archibald, http://www.html5rocks.com/en/tutorials/es6/promises/ */
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
resolve(req.response); /* PROMISE RESOLVED */
} else {
reject(Error(req.statusText)); /* PROMISE REJECTED */
}
};
req.onerror = function() { reject(Error("Network Error")); };
req.send();
});
}

Використання Promises

Після того, як ми створили Promise, ми повинні реально використовувати її. Для виконання promise-ified функції, ми можемо викликати її як і будь-яку звичайну функцію. Але, оскільки це promise, ми тепер маємо доступ до методу .then (), який ми можемо додати до функції і який буде виконаний, коли promise більше не знаходиться на розгляді.

Метод .then () приймає два необов'язкові параметри. По-перше, функція викликається, якщо promise виконано. По-друге, функція викликається, якщо promise буде відхилено.

get(url)
.then(function(response) {
/* successFunction */
}, function(err) {
/* errorFunction */
})

enter image description here

Обробка помилок

Оскільки обидві, успішна і помилкова, функції є необов'язковими, ми можемо розділити їх на дві .then () для кращого читання.

get(url)
.then(function(response) {
/* successFunction */
}, undefined)
.then(undefined, function(err) {
/* errorFunction */
})

Щоб зробити речі ще більш зручними для читання, ми використовуємо метод .catch(), який є скороченням для .then(undefined, errorFunction).

get(url)
.then(function(response) {
/* successFunction */
})
.catch(function(err) {
/* errorFunction */
})

enter image description here

Формування послідовності

Реальне значення в промісах, коли ми маємо декілька асинхронних функцій які нам потрібно виконати в певному порядку. Ми можемо зв’язати .then() і .catch() разом, щоб створити послідовність асинхронних функцій.

Ми робимо це, повертаючи іншу promise в межах успішної чи помилкової функції. Наприклад -

get(url)
.then(function(response) {
response = JSON.parse(response);
var secondURL = response.data.url
return get( secondURL ); /* Return another Promise */
})
.then(function(response) {
response = JSON.parse(response);
var thirdURL = response.data.url 
return get( thirdURL ); /* Return another Promise */
})
.catch(function(err) {
handleError(err);
});

Якщо promise з ланцюжка вирішена, вона буде рухатися далі до наступної успішної функції (.then() ) в послідовності. Якщо, з іншого боку, promise відхилена, вона перейде до наступної помилкової функція (.catch()) в послідовності.

enter link description here

Асинхронне виконання promises

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

Для цього нам потрібно використати два методи. Перший - метод Array.map() дозволяє нам виконати дію для кожного елемента масиву, і створює новий масив результатів цих дій.

Другий - метод Promise.all() повертає promise, який виконано тільки тоді коли всі проміси з масиву будуть вирішені. Якщо який-небудь один promise в межах масиву відхиляється, Promise.all () promise також відхиляється.

var arrayOfURLs = ['one.json', 'two.json', 'three.json', 'four.json'];
var arrayOfPromises = arrayOfURLs.map(get);
Promise.all(arrayOfPromises)
.then(function(arrayOfResults) {
/* Do something when all Promises are resolved */
})
.catch(function(err) {
/* Handle error is any of Promises fails */
})

enter image description here

Якщо ми подивимося на панелі Network в наших інструментах розробки, ми можемо бачити, що всі вибрані запити відбуваються паралельно.

enter image description here

Джерело перекладу

658 4

Схожі матеріали:

Коментарі:

Костя Третяк

19 Сер 2016 21:51

Коли ми витягуємо дані з деякого джерела, наприклад з API

Хоча бачу що так написано і в першоджерелі, але так не правильно казати, бо API - це Application Programming Interface, тобто інтерфейс певної програми, призначений для зовнішнього доступу через інші програми. В API немає даних, там можуть бути методи для отримання даних.

По-перше, функція викликається, якщо promise виконано. По-друге, функція викликається, якщо promise буде відхилено.

Там мається на увазі в першому аргументі передається функція, яка викликається у разі успішного завершення промісу, а в другому - функція, коли щось пішло не так

Ще можна додати, що виклик .catch() може бути єдиним на весь ланцюжок промісів. Це дуже зручно, бо можна скільки завгодно вкладати проміси один-в-один та робити скільки завгодно довгий ланцюжок промісів, і якщо в будь-якому із них відбудеться помилка, то зовнішній (чи наступний) .catch() обов'язково зловить цю помилку.

Костя Третяк

19 Сер 2016 22:04
Цей коментар прихований автором

Костя Третяк

19 Сер 2016 22:15

Забув уточнити, що є два варіанти отримання промісу. Перший простіший:

Promise.resolve(); // ну або ж Promise.reject();

Другий варіант той, який показано у цій публікації:

new Promise( function (resolve, reject)
{
  // Do some here
  if(err) return reject();
  resolve(data);
});

Костя Третяк

19 Сер 2016 22:31
Цей коментар прихований автором

Костя Третяк

19 Сер 2016 22:34

Ну і також не забувайте що ланцюжок промісів буде працювати як очікується, якщо кожен із них буде повертати також проміс (ну або просто дані, але лише якщо їх ми отримуємо синхронно).

Promise.resolve()
.then( function()
{
  var data = getDataFromDb();
  return data;
})
.then( function(data)
{
  console.log(data)
})
.catch( function(err)
{
  console.log(err);
})

Другий проміс може виконатись ще до завершення отримання даних в першому промісу, це трапиться якщо функція getDataFromDb() не повертає проміс. Якщо ж getDataFromDb() повертає таки проміс, то все працюватиме як очікується.

Авторизуйтесь, щоб залишити коментар.