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
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
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

Дивіться також