Promise.prototype.then()
Метод then()
(тоді) примірників Promise
приймає до двох аргументів – функцій зворотного виклику на випадок успіху й невдачі Promise
, відповідно. Він зберігає функції зворотного виклику в промісі, на якому викликаний, і негайно повертає інший об'єкт Promise
, завдяки чому можна утворювати ланцюжки з викликів інших методів промісів.
Спробуйте його в дії
Синтаксис
then(onFulfilled)
then(onFulfilled, onRejected)
Параметри
onFulfilled
Функція, що буде асинхронно виконана, коли цей проміс стане сповненим. Її повернене значення стає значенням сповнення проміса, поверненого з
then()
. Ця функція викликається з наступними аргументами:value
Значення, з яким проміс був сповнений.
Якщо цей аргумент не є функцією, то він тихо замінюється функцією ідентичності (
(x) => x
), яка просто передає значення сповнення далі.onRejected
Необов'язковеФункція, що буде асинхронно виконана, коли цей проміс стане відхиленим. Її повернене значення стає значенням сповнення проміса, поверненого з
then()
. Ця функція викликається з наступними аргументами:reason
Значення, з яким проміс був відхилений.
Якщо цей аргумент не є функцією, то він тихо замінюється функцією-викидачем (
(x) => { throw x; }
), яка викидає отримане значення відхилення як помилку.
Повернене значення
Негайно повертає новий Promise
. Цей новий проміс завжди при поверненні має стан очікування, незалежно від статусу поточного проміса.
Або onFulfilled
, або onRejected
буде викликано для обробки сповнення або відхилення поточного проміса. Такий виклик завжди відбувається асинхронно, навіть тоді, коли поточний проміс – уже залагоджено. Поведінка поверненого проміса (назвімо його p
) залежить від результату виконання обробника, згідно з конкретним набором правил. Якщо функція-обробник:
- повертає значення – то
p
сповнюється поверненим значенням як власним. - нічого не повертає – то
p
сповнюється значеннямundefined
як власним. - викидає помилку – то
p
відхиляється з викинутою помилкою як власним значенням. - повертає вже сповнений проміс – то
p
сповнюється значенням цього проміса як власним. - повертає вже відхилений проміс – то
p
відхиляється зі значенням цього проміса як власним. - повертає інший проміс в стані очікування – то
p
зберігає стан очікування, і стає сповненим або відхиленим зі значенням того іншого проміса як своїм власним – негайно після сповнення чи відхилення того проміса.
Опис
Метод then()
відкладає виконання функцій зворотного виклику до остаточного завершення проміса – або сповнення, або відхилення. Це примітивний метод промісів: протокол очікуваного об'єкта розраховує на те, що всі промісоподібні об'єкти надають доступ до методу then()
, а також методів catch()
і finally()
, обидва з яких працюють шляхом заклику методу then()
свого об'єкта.
Більше про обробника onRejected
– на довідковій сторінці catch()
.
Метод then()
повертає новий об'єкт-проміс, але вносить зміни до об'єкта-проміса, на якому викликаний, додаючи обробники до прихованого списку. Тож обробник зберігається вихідним промісом, і його час життя триває, щонайменше поки існує вихідний проміс. Наприклад, наступний приклад зрештою вичерпає пам'ять, навіть попри те, що повернений проміс не зберігається:
const pendingPromise = new Promise(() => {});
while (true) {
pendingPromise.then(doSomething);
}
Якщо викликати двічі на одному об'єкті-промісі метод then()
(замість утворення ланцюжка), то цей об'єкт-проміс матиме дві пари обробників залагодження. Усі обробники, прикріплені до одного об'єкта-проміса завжди викликаються в тому порядку, в якому вони були додані. Понад те, два проміси, повернені кожним з викликів then()
, почнуть окремі ланцюжки й не чекатимуть залагодження одне одного.
Очікувані об'єкти, що постають в ланцюжку then()
, завжди вирішуються: обробник onFulfilled
ніколи не отримує очікуваний об'єкт, і всі очікувані об'єкти, повернені будь-яким з обробників, завжди вирішуються перед переходом до наступного обробника. Так відбувається через те, що при конструюванні проміса функції resolve
і reject
, передані з executor
, зберігаються, і коли поточний проміс залагоджується, то викликається відповідна функція, зі значенням сповнення або причиною відхилення. Логіка вирішення приходить з функції resolve
, переданої конструктором Promise()
.
Метод then()
підтримує створення підкласів, а отже – його можна викликати на примірниках підкласів Promise
, і результатом такого виклику буде проміс типу підкласу. Тип поверненого значення можна налаштувати за допомогою властивості [Symbol.species]
.
Приклади
Використання методу then()
const p1 = new Promise((resolve, reject) => {
resolve("Успіх!");
// або
// reject(new Error("Помилка!"));
});
p1.then(
(value) => {
console.log(value); // Успіх!
},
(reason) => {
console.error(reason); // Помилка!
},
);
Нефункція замість одного з параметрів
Promise.resolve(1).then(2).then(console.log); // 1
Promise.reject(1).then(2, 2).then(console.log, console.log); // 1
Утворення ланцюжків
Метод then
повертає Promise
, що дає змогу утворювати ланцюжки промісів.
Якщо функція, передана в then
як обробник, повертає Promise
, то рівносильний Promise
буде наданий наступному then
у ланцюжку методів. Уривок коду нижче імітує асинхронний код за допомогою функції setTimeout
.
Promise.resolve("foo")
// 1. Отримати "foo", причепити "bar", і передати це як вирішене значення в наступний then
.then(
(string) =>
new Promise((resolve, reject) => {
setTimeout(() => {
string += "bar";
resolve(string);
}, 1);
}),
)
// 2. отримати "foobar", зареєструвати функцію зворотного виклику для роботи з цим рядком
// і вивести його в консоль, але не раніше повернення необробленого
// рядка в наступний then
.then((string) => {
setTimeout(() => {
string += "baz";
console.log(string); // foobarbaz
}, 1);
return string;
})
// 3. надрукувати корисні повідомлення про те, як запускатиметься код в цьому розділі
// до фактичної обробки рядка зімітованим асинхронним кодом у
// у попередньому блоку then.
.then((string) => {
console.log(
"Останній Then: ой... не потурбувалися створенням і поверненням проміса в попередньому then, тож порядок може бути трохи неочікуваним",
);
// Зверніть увагу, що `string` у цій точці не має частини 'baz'. Це
// так через те, що ми зімітували її асинхронне додавання за допомогою функції setTimeout
console.log(string); // foobar
});
// По порядку виводить:
// Останній Then: ой... не потурбувалися створенням і поверненням проміса в попередньому then, тож порядок може бути трохи неочікуваним
// foobar
// foobarbaz
Значення, повернене з then()
, вирішується так само, як Promise.resolve()
. А отже, очікувані об'єкти – підтримуються, і якщо повернене значення не є промісом, то воно неявно загортається в Promise
, а потім вирішується.
const p2 = new Promise((resolve, reject) => {
resolve(1);
});
p2.then((value) => {
console.log(value); // 1
return value + 1;
}).then((value) => {
console.log(value, "- Синхронне значення працює"); // 2 - Синхронне значення працює
});
p2.then((value) => {
console.log(value); // 1
});
Виклик then
повертає проміс, що врешті-решт відхиляється, якщо передана функція викидає помилку або повертає відхилений проміс.
Promise.resolve()
.then(() => {
// Змушує .then() повернути відхилений проміс
throw new Error("О ні!");
})
.then(
() => {
console.log("Не викликано.");
},
(error) => {
console.error(`Викликана функція onRejected: ${error.message}`);
},
);
На практиці нерідко бажано перехопити відхилені проміси, а не використовувати синтаксис then
з двома варіантами, як показано нижче.
Promise.resolve()
.then(() => {
// Змушує .then() повернути відхилений проміс
throw new Error("О ні!");
})
.catch((error) => {
console.error(`Викликана функція onRejected: ${error.message}`);
})
.then(() => {
console.log(
"Мене завжди викликають, навіть коли проміс попереднього then відхиляється",
);
});
У всіх інших випадках повернений проміс, зрештою, сповнюється. У наступному прикладі, перший then()
повертає число 42
, загорнуте у сповнений проміс, навіть попри те, що попередній проміс в ланцюжку був відхилений.
Promise.reject()
.then(
() => 99,
() => 42,
) // onRejected повертає число 42, котре загортається у сповнений проміс
.then((solution) => console.log(`Вирішено з ${solution}`)); // Вирішено з 42
Якщо onFulfilled
повертає проміс, то повернене значення then
буде сповнено або відхилено на основі остаточного стану цього проміса.
function resolveLater(resolve, reject) {
setTimeout(() => {
resolve(10);
}, 1000);
}
function rejectLater(resolve, reject) {
setTimeout(() => {
reject(new Error("Помилка"));
}, 1000);
}
const p1 = Promise.resolve("foo");
const p2 = p1.then(() => {
// Повернути тут проміс, котрий буде вирішений зі значенням 10 за 1 секунду
return new Promise(resolveLater);
});
p2.then(
(v) => {
console.log("вирішено", v); // "вирішено", 10
},
(e) => {
// не викликається
console.error("відхилено", e);
},
);
const p3 = p1.then(() => {
// Повернути тут проміс, котрий буде відхилений зі значенням 'Помилка' за 1 секунду
return new Promise(rejectLater);
});
p3.then(
(v) => {
// не викликається
console.log("вирішено", v);
},
(e) => {
console.error("відхилено", e); // "відхилено", 'Помилка'
},
);
Для реалізації однієї функції з API на основі Promise можна використати ланцюжок викликів.
function fetchCurrentData() {
// API fetch() повертає Promise. Ця функція
// пропонує подібний API, окрім того, що значення
// сповнення проміса цієї функції
// отримує більше обробки.
return fetch("current-data.json").then((response) => {
if (response.headers.get("content-type") !== "application/json") {
throw new TypeError();
}
const j = response.json();
// можливо, якісь дії зі
// значенням сповнення j, переданим користувачеві
// fetchCurrentData().then()
return j;
});
}
Асинхронність then()
Код нижче – приклад для демонстрації асинхронності метода then
.
// При використанні вирішеного промісу 'resolvedProm' для прикладу
// виклик функції 'resolvedProm.then(...)' негайно повертає новий проміс,
// однак його обробник '(value) => {...}' викликається асинхронно, як це показано спрацюваннями console.log.
// новий проміс присвоюється сталій 'thenProm',
// і цей thenProm вирішується значенням, поверненим обробником
const resolvedProm = Promise.resolve(33);
console.log(resolvedProm);
const thenProm = resolvedProm.then((value) => {
console.log(
`це викликається після кінця головного стеку. отримане значення: ${value}, повернене значення: ${
value + 1
}`,
);
return value + 1;
});
console.log(thenProm);
// За допомогою setTimeout можна відкласти виконання функції до тієї миті, коли стек порожній
setTimeout(() => {
console.log(thenProm);
});
// Виводить, по порядку:
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 33}
// Promise {[[PromiseStatus]]: "pending", [[PromiseResult]]: undefined}
// "це викликається після кінця головного стеку. отримане значення: 33, повернене значення: 34"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 34}
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
then()
|
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 |