Promise: примеры (JAVASCRIPT)

Асинхронные Promise в JavaScript с практическими примерами
Раздел: Асинхронность, Конструкторы
Promise(executor (function)): Promise

Основы Promise в JavaScript

Объект Promise (Обещание) используется для отложенных и асинхронных вычислений. Он представляет собой значение, которое может быть доступно сейчас, в будущем или никогда. Promise находится в одном из трех состояний: ожидание (pending), выполнено (fulfilled), отклонено (rejected).

Использование Promise характерно для операций, требующих времени: сетевые запросы, чтение файлов, таймеры. Конструктор принимает функцию-исполнитель (executor) с двумя параметрами: resolve и reject. Функция-исполнитель выполняется немедленно при создании промиса.

Аргументы конструктора:

  • executor: Функция (resolve, reject) => {}. Вызывается конструктором Promise. Код внутри этой функции инициирует асинхронную операцию.
  • resolve: Функция, вызываемая при успешном завершении. Передает результат операции.
  • reject: Функция, вызываемая при ошибке. Передает объект ошибки.

Возвращаемое значение: Конструктор возвращает новый объект Promise. Этот объект имеет методы .then(), .catch() и .finally() для обработки результатов.

Простые примеры использования Promise

Пример создания и обработки успешного обещания.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Данные получены'), 1000);
});
successPromise.then(result => console.log(result));
// Через 1 секунду:
Данные получены

Пример с обработкой ошибки.

const errorPromise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('Ошибка загрузки')), 1000);
});
errorPromise
  .then(result => console.log(result))
  .catch(error => console.error(error.message));
// Через 1 секунду:
Ошибка загрузки

Использование метода Promise.resolve() для немедленного создания выполненного промиса.

const immediatePromise = Promise.resolve('Мгновенное значение');
immediatePromise.then(value => console.log(value));
Мгновенное значение

Альтернативные подходы в JavaScript

Async/await: Синтаксический сахар над промисами для написания асинхронного кода в синхронном стиле. Предпочтителен для последовательных операций и улучшения читаемости.

async function fetchData() {
  try {
    const response = await fetch('api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

Callback-функции: Традиционный подход, где функция передается как аргумент и вызывается по завершению операции. Приводит к "аду колбэков" при сложной вложенности. Промисы решают эту проблему.

Event Emitters / Observables (RxJS): Используются для реактивного программирования и работы с потоками событий. Подходят для сценариев с множественными повторяющимися событиями.

Концепции в других языках программирования

Python (asyncio): Использует ключевые слова async и await для корутин. Объекты Future аналогичны Promise.

import asyncio
async def main():
    await asyncio.sleep(1)
    print('Готово')
asyncio.run(main())
# Через 1 секунду:
Готово

PHP (Promises): Реализации через библиотеки, например Guzzle. С версии 8.1 появились Fibers для асинхронности.

// Пример с библиотекой Guzzle
$promise = $httpClient->requestAsync('GET', 'url');
$promise->then(
    function ($response) { echo 'Успех'; },
    function ($reason) { echo 'Ошибка'; }
);

C# (Task): Тип Task и Task<T> с ключевыми словами async/await. Более тесно интегрированы в среду выполнения .NET.

public async Task<string> GetDataAsync()
{
    await Task.Delay(1000);
    return "Данные";
}

Распространенные ошибки разработчиков

1. Потеря обработки ошибок: Отсутствие блока .catch() приводит к тихому провалу.

const riskyPromise = new Promise((resolve, reject) => {
  reject(new Error('Проблема'));
});
// Ошибка не будет обработана, возможно предупреждение в консоли.

2. Вложенность промисов (Pyramid of Doom): Вместо цепочки методов создается сложная структура.

// Плохо
getUser().then(user => {
  getProfile(user).then(profile => {
    // ...
  });
});
// Хорошо
getUser()
  .then(user => getProfile(user))
  .then(profile => { /* ... */ });

3. Забытый return в цепочке then: Приводит к тому, что следующий then получает undefined.

Promise.resolve(5)
  .then(value => {
    value * 2; // Здесь нет return
  })
  .then(result => console.log(result)); // Выведет undefined

Эволюция Promise в современных версиях

Спецификация ES2020 добавила метод Promise.allSettled(). Он ждет завершения всех промисов, независимо от их статуса, и возвращает массив результатов с информацией о состоянии.

const promises = [
  Promise.resolve('Успех'),
  Promise.reject('Отказ'),
  Promise.resolve('Еще один успех')
];
Promise.allSettled(promises).then(results => {
  results.forEach(result => console.log(result.status, result.value || result.reason));
});
fulfilled Успех
rejected Отказ
fulfilled Еще один успех

Также ES2021 представил метод Promise.any(), который ожидает выполнения первого успешного промиса, игнорируя отклоненные. Если все промисы отклонены, возвращается AggregateError.

Сложные и специальные примеры

Имитация запросов с задержкой и использование Promise.all() для параллельного выполнения.

Пример javascript
const fetchUser = () => new Promise(resolve => setTimeout(() => resolve({id: 1, name: 'Иван'}), 800));
const fetchOrders = () => new Promise(resolve => setTimeout(() => resolve([1, 2, 3]), 500));

Promise.all([fetchUser(), fetchOrders()])
  .then(([user, orders]) => {
    console.log('Пользователь:', user.name);
    console.log('Заказы:', orders.length);
  })
  .catch(error => console.error('Ошибка в одном из запросов:', error));
// После ~800 мс (максимальной задержки):
Пользователь: Иван
Заказы: 3

Использование Promise.race() для создания таймаута операции.

Пример javascript
const dataFetch = new Promise(resolve => setTimeout(() => resolve('Данные с сервера'), 2000));
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Таймаут')), 1000));

Promise.race([dataFetch, timeout])
  .then(data => console.log(data))
  .catch(error => console.error(error.message));
// Через 1 секунду:
Таймаут

Создание промиса, который повторяет попытку операции при неудаче.

Пример javascript
function retryOperation(operation, retries) {
  return new Promise((resolve, reject) => {
    const attempt = (n) => {
      operation()
        .then(resolve)
        .catch(error => {
          if (n === 0) {
            reject(error);
          } else {
            console.log(`Попытка ${retries - n + 1} не удалась, пробую еще...`);
            attempt(n - 1);
          }
        });
    };
    attempt(retries);
  });
}
// Пример использования с функцией, которая иногда падает
const unstableFetch = () => new Promise((res, rej) => Math.random() > 0.3 ? res('OK') : rej('Временный сбой'));
retryOperation(unstableFetch, 3)
  .then(result => console.log('Итог:', result))
  .catch(finalError => console.error('Все попытки исчерпаны:', finalError));

JS Promise function comments

En
Promise Creates a promise for asynchronous operations