Конструктор Promise()
Конструктор Promise()
(проміс) створює об'єкти Promise
. Він використовується в основному для обгортання API, які використовують зворотні виклики, але ще не підтримують проміси.
Спробуйте його в дії
Синтаксис
new Promise(executor)
Примітка:
Promise()
можна конструювати лише за допомогоюnew
. Спроба викликати його безnew
викидаєTypeError
.
Параметри
executor
Функція
, що викликається конструктором. Вона отримує як параметри дві функції:resolveFunc
іrejectFunc
. Будь-які помилки, що виникають вexecutor
, призводять до відхилення проміса, а повернене значення ігнорується. Семантикаexecutor
детально описана нижче.
Повернене значення
Бувши викликаним за допомогою new
, конструктор Promise
повертає об'єкт проміса. Об'єкт проміса стає вирішеним, коли закликається або функція resolveFunc
, або функція rejectFunc
. Зверніть увагу, що якщо викликати resolveFunc
або rejectFunc
і передати інший об'єкт Promise
як аргумент, то можна сказати, що проміс стає "вирішеним", але ще не "залагодженим". Дивіться опис Promise для отримання додаткових пояснень.
Опис
Традиційно (до промісів) асинхронні задачі оброблялися за допомогою зворотних викликів.
readFile("./data.txt", (error, result) => {
// Ця функція зворотного виклику викликається, коли завдання виконано, з
// результатом – `error` або `result`. Будь-яка операція, що залежить від
// результату, повинна бути визначена в цій функції зворотного виклику.
});
// Код тут викликається зразу після того, як запит `readFile`
// запускається. Він не чекає виклику функції зворотного виклику, таким чином
// роблячи `readFile` "асинхронним".
Щоб скористатися перевагами поліпшення читабельності та можливостей мови, які пропонують проміси, конструктор Promise()
дозволяє перетворити API на основі зворотних викликів на API на основі промісів.
Примітка: Якщо ваша задача вже заснована на промісах, вам, ймовірно, не потрібен конструктор
Promise()
.
Функція executor
– це клієнтський код, що зв'язує результат виклику функції зворотного виклику з промісом. Ви, програміст, пишете функцію executor
. Очікується, що її сигнатура буде такою:
function executor(resolveFunc, rejectFunc) {
// Зазвичай – якась асинхронна операція, що приймає функцію зворотного виклику,
// подібна до функції `readFile` вище
}
Параметри resolveFunc
і rejectFunc
також є функціями, і їм можна дати будь-які фактичні імена. Їх сигнатури прості: вони приймають один параметр будь-якого типу.
resolveFunc(value); // виклик при вирішенні
rejectFunc(reason); // виклик при відхиленні
Параметр value
, переданий до resolveFunc
, може бути ще одним об'єктом-промісом, і в такому випадку стан новоствореного проміса буде "зав'язаний" на стан переданого проміса (як частина проміса вирішення). Функція rejectFunc
має семантику, близьку до інструкції throw
, тому reason
зазвичай є примірником Error
. Якщо value
або reason
відсутні, то проміс сповнюється або відхиляється з undefined
.
Стан завершення executor
має обмежений вплив на стан проміса:
- Повернене з
executor
значення ігнорується. Інструкціїreturn
всерединіexecutor
впливають лише на потік керування і змінюють те, чи виконуються певні частини функції, але не мають жодного впливу на значення сповнення проміса. Якщоexecutor
завершується і неможливо, щобresolveFunc
абоrejectFunc
були викликані в майбутньому (наприклад, немає запланованих асинхронних задач), то такий проміс залишається назавжди в стані очікування. - Якщо помилка викидається всередині
executor
, то проміс відхиляється, якщоresolveFunc
іrejectFunc
ще не були викликані.
Примітка: Існування промісів у стані очікування не заважає програмі завершитися. Якщо цикл подій порожній, програма завершується, не зважаючи на будь-які проміси у стані очікування (тому що вони обов'язково залишаються у стані очікування назавжди).
Ось нарис типового плину виконання:
- Коли конструктор породжує новий об'єкт
Promise
, він також породжує відповідну пару функційresolveFunc
іrejectFunc
; вони "припнуті" до об'єктаPromise
. - Функція
executor
зазвичай обгортає якусь асинхронну операцію, що надає API на основі зворотного виклику. Функція зворотного виклику (та, що передається в оригінальний API на основі зворотних викликів) визначається всередині кодуexecutor
, тому вона має доступ доresolveFunc
іrejectFunc
. - Функція
executor
викликається синхронно (як тількиPromise
створено) з функціямиresolveFunc
іrejectFunc
як аргументами. - Код всередині
executor
має можливість виконати якусь операцію. Примірник проміса повідомляється про завершення асинхронної задачі за допомогою побічного ефекту, спричиненогоresolveFunc
абоrejectFunc
. Побічний ефект полягає в тому, що об'єктPromise
стає "вирішеним".- Якщо спершу викликається
resolveFunc
, то передане значення буде вирішенням. Проміс може залишитися у стані очікування (якщо передається інший очікуваний об'єкт), стати сповненим (у більшості випадків, коли передається не очікуване значення) або відхилитися (у випадку недійсного значення вирішення). - Якщо першою викликається
rejectFunc
, то проміс миттєво відхиляється. - Коли вже була викликана одна з функцій вирішення (
resolveFunc
абоrejectFunc
), проміс залишається вирішеним. Тільки перший викликresolveFunc
абоrejectFunc
впливає на кінцевий стан проміса, і наступні виклики жодним чином не можуть ані змінити значення сповнення чи причину відхилення, ані перемкнути його кінцевий стан зі "сповненого" на "відхилений" або навпаки. - Якщо
executor
завершується викиданням помилки, то проміс відхиляється. Однак помилка ігнорується, якщо одна з функцій вирішення вже була викликана (таким чином, проміс вже вирішено). - Вирішення проміса не обов'язково змушує проміс стати сповненим або відхиленим (тобто залагодженим). Цей проміс може залишитися в стані очікування, якщо його вирішено іншим очікуваним об'єктом, але його кінцевий стан буде відповідати кінцевому стану цього вирішеного очікуваного об'єкта.
- Якщо спершу викликається
- Коли проміс залагоджено, він (асинхронно) закликає всі подальші обробники, прив'язані за допомогою
then()
,catch()
абоfinally()
. Значення сповнення або причина відхилення передаються виклику обробників сповнення та відхилення як вхідний параметр (дивіться Ланцюжки промісів).
Наприклад, API readFile
на основі зворотних викликів вище можна перетворити на API на основі промісів.
const readFilePromise = (path) =>
new Promise((resolve, reject) => {
readFile(path, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
readFilePromise("./data.txt")
.then((result) => console.log(result))
.catch((error) => console.error("Не вийшло прочитати дані"));
Функції зворотного виклику resolve
і reject
доступні лише всередині області видимості функції executor
, тобто не можна звернутися до них після конструювання проміса. Якщо потрібно сконструювати проміс до прийняття рішення щодо того, як його вирішувати, можна натомість скористатися методом Promise.withResolvers
, який видає функції resolve
і reject
.
Функція resolve
Функція resolve
має наступну логіку:
- Якщо вона викликана з таким же значенням, як і новостворений проміс (проміс, до якого вона "припнута"), то цей проміс відхиляється з помилкою
TypeError
. - Якщо вона викликається з не очікуваним значенням (примітивом, або об'єктом, чию властивість
then
не можна викликати, в тому числі коли вона відсутня), то її проміс негайно сповнюється цим значенням. - Якщо вона викликається з очікуваним значенням (наприклад, іншим примірником
Promise
), то зберігається методthen
цього значення – він буде викликаний у майбутньому (і завжди асинхронно). Цей методthen
буде викликаний з двома функціями зворотного виклику, які будуть двома новими функціями з такою ж логікою, як у функційresolveFunc
іrejectFunc
, переданих у функціюexecutor
. Якщо виклик цього методуthen
викидає помилку, то поточний проміс відхиляється з викинутою помилкою.
В останньому випадку, це означає, що подібний код:
new Promise((resolve, reject) => {
resolve(thenable);
});
Приблизно рівносильний такому:
new Promise((resolve, reject) => {
try {
thenable.then(
(value) => resolve(value),
(reason) => reject(reason),
);
} catch (e) {
reject(e);
}
});
Окрім того, що в випадку resolve(thenable)
:
- Функція
resolve
викликається синхронно, тож повторний викликresolve
абоreject
ніяк не діє, навіть коли обробники, приєднані за допомогоюanotherPromise.then()
, ще не викликані. - Метод
then
викликається асинхронно, тож проміс ніколи не буде миттєво сповненим, якщо передається очікуваний об'єкт.
У зв'язку з тим, що resolve
викликається знову з тим, що thenable.then()
передає йому як value
, функція вирішення може сплющувати вкладені очікувані об'єкти, де очікуваний об'єкт викликає свій обробник onFulfilled
з іншим очікуваним об'єктом. Це означає, що обробник сповнення справжнього проміса ніколи не отримає очікуваний об'єкт як значення сповнення.
Приклади
Перетворення API на основі зворотних викликів на API на основі промісів
Щоб надати функції функціональність промісів, вона повинна повертати проміс, викликаючи функції resolve
і reject
у відповідний час.
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
}
Вплив виклику resolveFunc
Виклик resolveFunc
змушує проміс стати вирішеним, тож подальші виклики resolveFunc
або rejectFunc
не мають жодного впливу. Однак проміс може перебувати в будь-якому стані: очікування, сповненості або відхиленості
Цей проміс pendingResolved
вирішений в момент створення, тому що він вже "замкнений" на відповідність остаточному станові внутрішнього проміса, і подальший виклик resolveOuter
або rejectOuter
або викидання помилки пізніше в функції-виконавці не має жодного впливу на його остаточний стан. Однак внутрішній проміс все ж перебуває в стані очікування, поки не мине 100 мс, тож зовнішній проміс також перебуває у стані очікування:
const pendingResolved = new Promise((resolveOuter, rejectOuter) => {
resolveOuter(
new Promise((resolveInner) => {
setTimeout(() => {
resolveInner("внутрішній");
}, 100);
}),
);
});
Цей проміс fulfilledResolved
стає сповненим в момент вирішення, тому що він вирішується значенням, яке не є очікуваним об'єктом. Однак коли він створюється, він є невирішеним, тому що ані resolve
, ані reject
ще не були викликані. Невирішений проміс обов'язково перебуває в стані очікування:
const fulfilledResolved = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("зовнішній");
}, 100);
});
Виклик rejectFunc
очевидно призводить до відхилення проміса. Однак є ще два способи, якими можна змусити проміс миттєво відхилитися, навіть коли викликається функція resolveFunc
.
// 1. Вирішення проміса самим собою
const rejectedResolved1 = new Promise((resolve) => {
// Примітка: resolve має бути викликана асинхронно,
// щоб ініціалізувалася змінна rejectedResolved1
setTimeout(() => resolve(rejectedResolved1)); // TypeError: Chaining cycle detected for promise #<Promise>
});
// 2. Вирішення об'єктом, що викидає помилку, коли відбувається звертання до властивості `then`
const rejectedResolved2 = new Promise((resolve) => {
resolve({
get then() {
throw new Error("Не можна отримати властивість then");
},
});
});
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Promise() constructor
|
Chrome Full support 32 | Edge Full support 12 | Firefox Full support 29 | Internet Explorer No support Ні | Opera Full support 19 | Safari Full support 8 | WebView Android Full support 4.4.3 | Chrome Android Full support 32 | Firefox for Android Full support 29 | Opera Android Full support 19 | Safari on iOS Full support 8 | Samsung Internet Full support 2.0 | Deno Full support 1.0 | Node.js Full support 0.12.0 |