Promise в JavaScript

Прежде чем изучать эту главу, вам нужно понять, что такое асинхронное программирование. Вы можете обратиться к:Асинхронное программирование на JavaScript

Promise — это класс, предоставленный ECMAScript 6 и предназначенный для более элегантного написания сложных асинхронных задач.

Promise — это объект в JavaScript, используемый для обработки асинхронных операций. Он представляет собой конечное завершение (или неудачу) асинхронной операции и её результирующее значение.

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

Promise имеет три состояния:

  • pending: начальное состояние, ни fulfilled, ни rejected
  • fulfilled: означает, что операция успешно завершена
  • rejected: означает, что операция завершилась неудачей

Пример

const myPromise = new Promise((resolve, reject) => {
  // код асинхронной операции
 
  if (/* Операция выполнена успешно */) {
    resolve('Успешный результат'); // Изменить состояние Promise на fulfilled
  } else {
    reject('Причина ошибки'); // Изменить состояние Promise на rejected
  }
});

Поддержка браузеров

Поскольку Promise появился в ES6, некоторые старые браузеры его не поддерживают. Apple Safari 10 и Windows Edge 14 и выше начали поддерживать возможности ES6.

Ниже показана поддержка Promise в браузерах:

Chrome 58 Edge 14 Firefox 54 Safari 10 Opera 55

Как использовать Promise

Метод then()

then()Этот метод используется для указания функций обратного вызова, когда состояние Promise изменяется на fulfilled или rejected.

Пример

myPromise.then(
  (result) => {
    // Обработка случая успеха
    console.log('Success:', result);
  },
  (error) => {
    // Обработка случая ошибки
    console.error('Failure:', error);
  }
);

Метод catch()

catch()Этот метод специально используется для обработки случаев, когда Promise находится в состоянии rejected.

Пример

myPromise
  .then((result) => {
    console.log('Success:', result);
  })
  .catch((error) => {
    console.error('Failure:', error);
  });

Метод finally()

finally()Этот метод выполняется независимо от конечного состояния Promise.

Пример

myPromise
  .then((result) => {
    console.log('Success:', result);
  })
  .catch((error) => {
    console.error('Failure:', error);
  })
  .finally(() => {
    console.log('Операция завершена');
  });

Цепочка Promise

Мощная особенность Promise заключается в том, что можно объединять в цепочку несколько асинхронных операций.

Пример

doFirstThing()
  .then((result) => doSecondThing(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log('Итоговый результат:', finalResult);
  })
  .catch((error) => {
    console.error('Ошибка где-то в цепочке:', error);
  });

Статические методы Promise

Promise.all()

Ожидает завершения всех Promise или отказа любого одного Promise.

Пример

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    // results — это массив, содержащий все результаты Promise
    console.log(results);
  })
  .catch((error) => {
    // Сюда попадаем, если любой Promise завершится с ошибкой
    console.error(error);
  });

Promise.race()

Возвращает результат первого Promise, который завершится (независимо от успеха или неудачи).

Пример

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    // Используйте результат первого завершившегося Promise
    console.log(result);
  })
  .catch((error) => {
    // Если первый завершившийся Promise отклонён
    console.error(error);
  });

Promise.resolve() и Promise.reject()

Быстро создать выполненный или отклонённый Promise.

Пример

const resolvedPromise = Promise.resolve('Immediately resolved value');
const rejectedPromise = Promise.reject('Immediately rejected reason');

Примеры практического применения

Пример 1: Использование Promise для обработки AJAX-запросов

Пример

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => reject(new Error('Ошибка сети'));
    xhr.send();
  });
}

fetchData('https://api.example.com/data')
  .then((data) => {
    console.log('Данные успешно получены:', data);
  })
  .catch((error) => {
    console.error('Не удалось получить данные:', error);
  });

Пример 2: Использование цепочки Promise для обработки нескольких асинхронных операций

Пример

function getUser(userId) {
  return fetch(`/users/${userId}`);
}

function getPosts(userId) {
  return fetch(`/users/${userId}/posts`);
}

getUser(123)
  .then((user) => {
    console.log('Получение информации о пользователе:', user);
    return getPosts(user.id);
  })
  .then((posts) => {
    console.log('Получение постов пользователя:', posts);
  })
  .catch((error) => {
    console.error('Операция не удалась:', error);
  });

Типичные проблемы и лучшие практики

1. Избегайте вложенности промисов

Неправильный подход (Promise-версия ада колбэков):

Пример

doFirstThing().then((firstResult) => {
  doSecondThing(firstResult).then((secondResult) => {
    doThirdThing(secondResult).then((thirdResult) => {
      console.log(thirdResult);
    });
  });
});

Правильный подход (использовать цепочки):

Пример

doFirstThing()
  .then(doSecondThing)
  .then(doThirdThing)
  .then((finalResult) => {
    console.log(finalResult);
  });

2. Всегда обрабатывайте отклонения

Если не обрабатывать отклонения Promise, это приведёт к ошибкам "uncaught promise rejection". Всегда используйте.catch()илиtry/catch(в async/await) для обработки ошибок.

3. Промисы не поддерживают отмену

После создания Promise нельзя отменить. Если вам нужна возможность отмены, рассмотрите использование AbortController или других паттернов.

4. Часто задаваемые вопросы

Вопрос: Можно ли изменить порядок then, catch и finally на обратный?

Ответ: Да, эффект абсолютно тот же. Но это не рекомендуется; лучше писать программу в порядке then-catch-finally.

Вопрос: Кроме блока then, можно ли использовать два других блока несколько раз?

Ответ: Да. finally, как и then, выполняется по порядку, но выполнится только первый блок catch, если только в блоке catch не возникнет исключение. Поэтому лучше располагать только один блок catch и один блок finally.

Вопрос: Как прервать блок then?

Ответ: Блок then по умолчанию выполняется последовательно сверху вниз; return не может его прервать. Можно использовать throw, чтобы перейти к catch и тем самым прервать выполнение.

Вопрос: Когда целесообразно использовать Promise вместо традиционных функций обратного вызова?

Ответ: Когда несколько асинхронных операций должны выполняться последовательно. Например, если вы хотите последовательно проверить имя пользователя и пароль через асинхронные методы, нужно сначала асинхронно проверить имя пользователя, затем асинхронно проверить пароль — это ситуация, хорошо подходящая для Promise.

Вопрос: Promise — это метод преобразования асинхронного в синхронное?

Ответ: Вовсе нет. Promise — это просто более удобный стиль программирования.

Вопрос: Когда нужно написать ещё один then вместо продолжения программирования в текущем then?

Ответ: Когда нужно вызвать другую асинхронную задачу.


Promise и async/await

В ES2017 появились async/await, которые основаны на Promise и предоставляют более интуитивный синтаксис для обработки асинхронных операций.

Пример

async function fetchData() {
  try {
    const user = await getUser(123);
    const posts = await getPosts(user.id);
    console.log('User posts:', posts);
  } catch (error) {
    console.error('Не удалось получить данные:', error);
  }
}

Хотя async/await более читаем, понимание основных концепций Promise всё ещё очень важно, потому что это основа async/await.

Другие расширения