Вы уже знаете, что промисы в JavaScript — это как обещания в реальной жизни: "Я сделаю это, когда приду домой". Но промисы умеют гораздо больше, чем просто .then() и .catch(). В этой статье мы разберём продвинутые приёмы, которые помогут вам писать чистый и производительный код. Особенно это полезно, если вы переходите с PHP на JavaScript — здесь своя философия асинхронности.

Почему промисы важны для PHP-разработчика

PHP — синхронный язык. Вы пишете запрос к базе данных, и скрипт ждёт, пока он выполнится. В JavaScript всё иначе: операции ввода-вывода (запросы к API, чтение файлов) не блокируют выполнение. Промисы позволяют управлять этими асинхронными операциями без "ада колбэков". Если вы привыкли к очередям в PHP (например, через RabbitMQ), то промисы — это аналог, только на стороне клиента или сервера (Node.js).

Основные методы промисов: краткий обзор

Прежде чем перейти к продвинутым техникам, вспомним базовые методы:

МетодОписание
Promise.resolve()Создаёт промис, который сразу выполняется успешно.
Promise.reject()Создаёт промис, который сразу завершается с ошибкой.
Promise.all()Ждёт выполнения всех промисов. Если один упал — падает весь.
Promise.race()Возвращает результат первого завершившегося промиса (успех или ошибка).
Promise.allSettled()Ждёт завершения всех промисов и возвращает их статусы (fulfilled/rejected).
Promise.any()Возвращает первый успешно выполненный промис. Если все упали — ошибка.

Эти методы — ваш арсенал для работы с параллельными задачами.

Promise.all: запускаем всё и сразу

Метод Promise.all принимает массив промисов и ждёт, пока все они завершатся. Если хотя бы один промис завершится с ошибкой, весь Promise.all упадёт. Это похоже на транзакцию в базе данных: либо все операции выполнены, либо ни одна.

Пример: загружаем данные о пользователе и его заказах параллельно.

const userPromise = fetch('/api/user/1');
const ordersPromise = fetch('/api/orders?userId=1');

Promise.all([userPromise, ordersPromise])
  .then(([userResponse, ordersResponse]) => {
    return Promise.all([userResponse.json(), ordersResponse.json()]);
  })
  .then(([user, orders]) => {
    console.log('Пользователь:', user);
    console.log('Заказы:', orders);
  })
  .catch(error => {
    console.error('Ошибка загрузки:', error);
  });

Обратите внимание: мы сначала дожидаемся ответов от сервера, а потом парсим JSON. Это цепочка промисов.

Promise.race: кто быстрее, тот и прав

Promise.race возвращает результат первого завершившегося промиса. Это может быть как успех, так и ошибка. Полезно, когда нужно установить тайм-аут для запроса.

Пример: если запрос к API не отвечает 5 секунд, показываем заглушку.

const apiCall = fetch('/api/data');
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('Тайм-аут')), 5000);
});

Promise.race([apiCall, timeout])
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Ошибка или тайм-аут:', error));

В PHP вы бы сделали set_time_limit(), но здесь мы управляем этим на уровне промисов.

Promise.allSettled: терпеливый наблюдатель

В отличие от Promise.all, этот метод не падает при первой ошибке. Он ждёт завершения всех промисов и возвращает массив объектов с полями status (fulfilled/rejected) и value или reason. Идеально для задач, где нужно обработать частичные успехи.

Пример: проверяем доступность нескольких серверов.

const urls = ['https://server1.com', 'https://server2.com', 'https://server3.com'];
const promises = urls.map(url => fetch(url));

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`${urls[index]} доступен`);
      } else {
        console.error(`${urls[index]} недоступен: ${result.reason}`);
      }
    });
  });

В PHP вы бы использовали try-catch в цикле, но здесь всё лаконичнее.

Цепочки промисов и обработка ошибок

Одна из сильных сторон промисов — возможность строить цепочки. Каждый .then() возвращает новый промис, и вы можете передавать данные дальше. Но будьте внимательны с ошибками: если не поставить .catch() в конце, ошибка "пролетит" мимо.

Лайфхак: всегда завершайте цепочку вызовом .catch(). И не забывайте, что ошибки можно обрабатывать на каждом этапе.

Правило: Если вы используете Promise.all или Promise.race, всегда добавляйте .catch() для обработки ошибок. Иначе приложение может "молча упасть".

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

fetch('/api/user')
  .then(response => {
    if (!response.ok) throw new Error('Ошибка сети');
    return response.json();
  })
  .then(user => {
    return fetch(`/api/orders?userId=${user.id}`);
  })
  .then(response => response.json())
  .then(orders => {
    console.log('Заказы:', orders);
  })
  .catch(error => {
    console.error('Что-то пошло не так:', error);
  });

В PHP вы бы написали try-catch, но здесь цепочка более гибкая.

Практический совет: комбинируем методы

Иногда нужно загрузить несколько ресурсов, но если один упал — не страшно. Используйте Promise.allSettled, а затем отфильтруйте успешные результаты. Или наоборот: если важен хотя бы один успех — берите Promise.any.

Пример: загружаем аватарки пользователей. Если одна не загрузилась — показываем заглушку.

const avatarUrls = ['/avatars/1.jpg', '/avatars/2.jpg', '/avatars/3.jpg'];
const promises = avatarUrls.map(url => fetch(url));

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        // Показываем аватарку
      } else {
        // Показываем заглушку
      }
    });
  });

Это напоминает обработку очередей в PHP, где вы проверяете статус каждой задачи.

Заключение

Промисы в JavaScript — это мощный инструмент, который заменяет колбэки и делает код читаемым. Для PHP-разработчика освоение Promise.all, Promise.race и Promise.allSettled — это шаг к пониманию асинхронного программирования. Начните с малого: замените вложенные колбэки на цепочки промисов. Потом добавьте параллельное выполнение. И не забывайте про обработку ошибок. В итоге ваш код станет быстрее и надёжнее.

Студия WNDER