Главная » Promise(ES6) - другой способ работы с обратными вызовами

Promise(ES6) - другой способ работы с обратными вызовами


06.07.2021, 23:12
Promise - дословно "обещание"

Благодаря использованию промисов, одна часть кода может выполняться лишь только после того, как будет выполнена другая часть. Промисы, таким образом, упрощают процесс асинхронного программирования.

Промис - это конечный результат выполнения асинхронной операции.

Для промиса существует три его возможных состояния:

Pending(ожидание) - асинхронная операция выполняется, промис в состоянии ожидания

Fulfilled(успешное выполнение) - операция выполнена успешно и промисам присвоено определённое значение

Rejected(выполнено с ошибкой) - произошла ошибка во время выполнения операции

Им соответствуют следующие значения для свойства result ("результат"):

в начале undefined

далее value (при вызове resolve(value)), либо error (при вызове reject(error))

Синтаксис создания промиса:

Код

let promise = new Promise(function(resolve, reject) {
/* здесь автоматически вызываемая функция, в которой можно делать
любые синхронные операции, в результате завершения которых необходимо вызвать
либо функцию resolve(при успешном выполнении), либо reject(при ошибке) */
})

Функция, переданная в new Promise, называется исполнителем (executor) и она запускается автоматически, после того, как промис создан.

Данная функция содержит в себе "создающий" код (он занимает какое-то время и что-то делает, например, загружает данные по сети).

Результат создающего кода используется "потребляющим" кодом, а промис связывает создающий и потребляющий код.

Аргументы resolve и reject - это callback-функции, которые предоставляются JavaScript.

После того, как функция-исполнитель получает результат, вызывается одна из callback функций:

resolve(value) - в случае успешного завершения (результат value)

reject(error) - в случае ошибки (error - объект ошибки)

В итоге, функция-исполнитель переводит promise в одно из состояний:

а) state: "fulfilled"
result: value

б) state: "rejected"
result: error

Пример "успешно выполненного" промиса

Код

let promise = new Promise(function(resolve,reject) {
  /* через 1 секунду сообщить, что задача выполнена с результатом "done" */
  setTimeout(() => resolve("Успешно"), 1000);
});

Пример промиса с ошибкой:

Код

let promise = new Promise(function(resolve, reject) {
  /* спустя одну секунду сообщить, что задача выполнена с ошибкой */
  setTimeout(() => reject(new Error("Ошибка!")), 1000);
});


Функция-исполнитель выполняет задачу, требующую времени, затем, в зависимости от того, успешно выполнена задача или с ошибкой, вызывается одна из функций-каллбэков, resolve или reject, изменяя тем самым состояние соотвествующего объекта promise

Любой промис, будь то успешный, либо отклонённый, считается завершённым, и его состояние больше нельзя изменить (все последующие вызовы resolve, либо reject, будут игнорироваться)

Код

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); /* игнорируется */
  setTimeout(() => resolve("…")); /* игнорируется */
});


Результат обрабатывается с помощью методов .then(), .catch(), .finally(), через которые регистрируются функции-потребители результатов успешного промиса.

Метод .then()

синтаксис метода:

Код

promise.then(
  function(result) { /* обработает успешное выполнение */ },
  function(error) { /* обработает ошибку */ }
);

Первая функция выполнится, если промис перейдёт в состояние «выполнен успешно», и получает результат.

Вторая функция - если промис перейдёт в состояние «выполнен с ошибкой», и получает ошибку.

Пример работы при успешно выполненном запросе:

Код

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

/* resolve запустит первую функцию, переданную в .then */
promise.then(
  result => alert(result), /* выведет "done!" через одну секунду */
  error => alert(error) /* не будет запущена */
);

Пример работы при возникновении ошибки:

Код

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Ошибка!")), 1000);
});

/* reject запустит вторую функцию, переданную в .then */
promise.then(
  result => alert(result), /* не будет запущена */
  error => alert(error) /* выведет "Ошибка!" спустя одну секунду */
);

Если интересует только случай успешного выполнения задачи, в then() можно передавать только одну функцию:

Код

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); /* выведет "done!" спустя одну секунду */

Метод .catch()

Методом catch(функция_обработки_ошибок) можно воспользоваться для обработки ошибок (ещё один вариант обработки ошибок заключается в передаче null в качестве первого аргумента для метода .then())

Другими словами, записи внизу равнозначны по функционалу:

Код

.catch(errorFunction);

.then(null, errorFunction); /* для обработки ошибок .catch() это сокращённый вариант .then() */

Пример работы:

Код

let promise = new Promise(function(resolve,reject) {
  setTimeout(() => reject(new Error('Ошибка!')), 1000);
});

promise.catch(console.log); /* выведет в консоль "Error: Ошибка!" через одну секунду */

Метод .finally()

Этот метод не предназначен для обработки результата промиса, он пропускает его дальше, например, к .then() или .catch()

Код

new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Промис завершён"))
  .then(result => alert(result)); /* .then обработает результат */

Код

new Promise((resolve, reject) => {
  throw new Error("error");
})
  .finally(() => alert("Промис завершён"))
  .catch(err => alert(err)); /* .catch обработает объект ошибки */

Вызов метода .finally(f) аналогичен вызову .then(f, f) тем, что функция f выполнится в любом случае, независимо от того, завершится промис успешно или с ошибкой.

Цепочка промисов (chaining)

Смысл цепочки промисов в том, что результат начального промиса передаётся по цепочке обработчиков .then

Пример цепочки:

Код

new Promise(function (resolve, reject) {

  setTimeout(() => resolve(1), 1000);  

}).then(function (result) {  

  console.log(result); /* 1 */
  return result * 2;

}).then(function (result) {  

  console.log(result); /* 2 */
  return result * 2;

}).then(function (result) {

  console.log(result); /* 4 */
  return result * 2;

});

Вызов promise.then вернёт тоже промис, к которому мы также применяем .then и так далее по цепочке.

Методы Promise.all() и Promise.race()

Напишем функцию test, с аргументом time, она будет возвращать нам промис, который "зарезолвится" через то количество времени, которое мы передадим в time:

Код

const test = time => {
  return new Promise(resolve => {
  setTimeout(() => resolve(), time);
  });
};

протестируем её работу:

Код

test(1000).then(() => console.log('1000 ms')); /* "1000 ms" сообщение появится в консоли через 1 секунду */
test(2000).then(() => console.log('2000 ms')); /* "2000 ms" собщение появится в консоли через 2 секунды */

Метод Promise.all() возвращает промис, который выполнится после того, как выполнятся все промисы, переданные в виде перечисляемого аргумента, например, в виде массива промисов (либо если любой из перечисляемых в аргументе промисов будет отклонён)

Данный метод позволяет нам убедиться, что все промисы успешно выполнены.

Код

Promise.all([test(1000), test(2000)]).then(() => {
  console.log('Все промисы выполнились!'); /* Сообщение появится в консоли через 2 секунды */
});

Promise.all() получает в качестве аргумента массив (или другой итерируемый объект) промисов и возвращает промис, который ждёт, пока все переданные промисы завершатся, и переходит в состояние «выполнено» с массивом их результатов.

Если какой-либо из промисов завершится ошибкой, то результатом Promice.all будет эта ошибка, а остальные промисы проигнорируются, даже если будут выполнены успешно.

Метод Promice.race() также получает в качестве аргумента массив или иной итерируемый объект с промисами, и возвращает новый промис, результатом работы будет только первый успешно выполнившийся промис из списка, остальные игнорируются.

Код

Promise.race([test(1000), test(2000)]).then(() => {
  console.log('Все промисы выполнились!'); /* Сообщение появится в консоли через 1 секунду */
});


КОММЕНТАРИИ (0)