Открыть сервис

Промисы

Промис (от англ. promise — обещание) — это объект, представляющий собой результат асинхронной операции, который может быть доступен в будущем. Промисы являются одним из основных механизмов работы с асинхронным кодом в языках программирования, в первую очередь в JavaScript, где они были введены стандартом ECMAScript 6 (ES2015) как альтернатива колбэкам (callback-функциям). Промис гарантирует, что после завершения асинхронной операции (например, запроса к серверу, чтения файла или таймера) будет вызвана одна из двух функций: resolve (успешное завершение) или reject (ошибка). Это позволяет избежать так называемого «ада колбэков» (callback hell) — многоуровневой вложенности функций, усложняющей чтение и поддержку кода.

История и предпосылки появления

До появления промисов в JavaScript основным способом обработки асинхронных операций были колбэки. Однако при последовательном выполнении нескольких асинхронных действий код становился сильно вложенным, что затрудняло его отладку и расширение. В начале 2010-х годов сообщество разработчиков начало активно искать альтернативы. В 2012 году был предложен стандарт Promises/A+, который описывал поведение промисов. На его основе были созданы библиотеки, такие как Q (Крис Коваль) и Bluebird (Петар Славов). В 2015 году спецификация ECMAScript 6 включила нативный объект Promise, что сделало промисы частью языка. Позднее, в ECMAScript 2017, появились синтаксические конструкции async/await, которые позволяют работать с промисами в более синхронном стиле, но под капотом всё равно используют промисы.

Классификация и основные понятия

Промисы можно классифицировать по их состоянию и типу.

Состояния промиса

Промис может находиться в одном из трёх состояний:

  • Pending (ожидание) — начальное состояние, операция ещё не завершена.
  • Fulfilled (выполнено) — операция завершена успешно, промис имеет значение (результат).
  • Rejected (отклонено) — операция завершилась ошибкой, промис имеет причину (ошибку).

После перехода из состояния pending в fulfilled или rejected промис становится «завершённым» (settled) и не может изменить своё состояние.

Типы промисов

По способу создания и использования различают:

  • Нативные промисы — встроенные в язык (например, new Promise() в JavaScript).
  • Промисы из библиотек — реализованные сторонними разработчиками (например, $.Deferred() в jQuery, Promise в Bluebird).
  • Промисы в других языкахконцепция промисов существует и в других языках, хотя под разными названиями: в Python — Future (модуль concurrent.futures), в C# — Task (TPL), в Java — CompletableFuture (Java 8). В Rust — Future (трейт std::future::Future).

Устройство и принцип работы

Промис — это объект, который принимает функцию-исполнитель (executor) с двумя параметрами: resolve и reject. Функция-исполнитель запускается немедленно при создании промиса и содержит асинхронный код. Когда асинхронная операция завершается, вызывается resolve (с результатом) или reject (с ошибкой). После этого промис переходит в соответствующее состояние.

Основные методы промиса:

  • .then(onFulfilled, onRejected) — регистрирует обработчики для успешного и ошибочного завершения. Возвращает новый промис, что позволяет создавать цепочки.
  • .catch(onRejected) — регистрирует обработчик только для ошибки. Эквивалентен .then(null, onRejected).
  • .finally(onFinally) — регистрирует обработчик, который выполняется в любом случае (после успеха или ошибки), не изменяя результат.

Цепочки промисов

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

``javascript fetch('/api/data') .then(response => response.json()) .then(data => processData(data)) .catch(error => console.error(error)); ``

В этом примере каждый .then() ждёт завершения предыдущего промиса, а .catch() обрабатывает любую ошибку, возникшую на любом этапе цепочки.

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

Кроме методов экземпляра, у объекта Promise есть статические методы:

  • Promise.all(iterable) — принимает массив промисов и возвращает промис, который выполняется, когда все переданные промисы выполнены успешно, или отклоняется при первой ошибке. Результат — массив результатов.
  • Promise.allSettled(iterable) — возвращает промис, который выполняется после завершения всех переданных промисов (независимо от их статуса). Результат — массив объектов с полями status и value или reason.
  • Promise.race(iterable) — возвращает промис, который выполняется или отклоняется, когда выполняется или отклоняется первый из переданных промисов.
  • Promise.any(iterable) — возвращает промис, который выполняется, когда выполняется любой из переданных промисов, или отклоняется, если все промисы отклонены.
  • Promise.resolve(value) — создаёт промис, который сразу выполняется с переданным значением.
  • Promise.reject(reason) — создаёт промис, который сразу отклоняется с переданной причиной.

Применение

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

Веб-разработка (JavaScript)

  • Запросы к серверу (AJAX, Fetch API) — основной сценарий. Функция fetch() возвращает промис, который разрешается с объектом Response.
  • Работа с файлами — чтение и запись файлов в браузере (File API) или на сервере (Node.js, fs.promises).
  • Таймеры и анимации — промисы позволяют дождаться завершения таймера или анимации.
  • Базы данных — асинхронные запросы к базам данных (например, MongoDB, PostgreSQL) через драйверы, поддерживающие промисы.
  • Обработка событий — промисы могут быть использованы для ожидания одного события из нескольких.

Серверное программирование

В среде Node.js промисы являются основой для работы с асинхронными операциями ввода-вывода (I/O). Модуль fs/promises предоставляет промис-версии функций для работы с файловой системой. Многие библиотеки и фреймворки (Express, Koa, NestJS) поддерживают промисы и async/await.

Другие языки

В Python модуль asyncio использует корутины, которые по сути являются промисами. В C# Task и Task<T> используются для асинхронного программирования с ключевыми словами async и await. В Java CompletableFuture позволяет комбинировать асинхронные операции. В Rust Future — это трейт, который реализуется асинхронными функциями.

Преимущества и недостатки

Преимущества

  • Читаемость кода — цепочки промисов более читаемы, чем вложенные колбэки.
  • Обработка ошибок — единый обработчик .catch() для всей цепочки.
  • Композиция — статические методы (Promise.all, Promise.race) позволяют управлять несколькими асинхронными операциями.
  • Неизменяемость — промис не может быть выполнен или отклонён дважды, что предотвращает случайные ошибки.

Недостатки

  • Сложность отладкистек вызовов в цепочке промисов может быть неполным, что затрудняет поиск ошибок.
  • Проблема «промисного ада» — при чрезмерном использовании цепочек код может стать громоздким, хотя это решается с помощью async/await.
  • Необходимость явного возврата — в цепочке промисов нужно явно возвращать значение, чтобы оно было передано следующему .then().
  • Отсутствие отмены — нативные промисы не поддерживают отмену операции. Для этого используются сторонние библиотеки (например, AbortController в браузере).

Сравнение с альтернативами

Колбэки

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

Async/await

Async/await — это синтаксический сахар над промисами, появившийся в ECMAScript 2017. Ключевое слово async перед функцией делает её возвращающей промис, а await приостанавливает выполнение функции до завершения промиса. Async/await делает асинхронный код похожим на синхронный, что улучшает читаемость. Однако под капотом он всё равно использует промисы.

RxJS (Reactive Extensions)

RxJS — это библиотека для реактивного программирования, использующая Observable (наблюдаемые потоки). В отличие от промисов, которые представляют одно значение в будущем, Observable может представлять поток значений во времени. RxJS предоставляет мощные операторы для комбинирования, фильтрации и трансформации потоков, но имеет более высокий порог входа.

Интересные факты

  • Спецификация Promises/A+ была разработана сообществом и не является официальным стандартом ECMAScript, но нативные промисы в JavaScript ей соответствуют.
  • В некоторых языках, например в Haskell, концепция промисов реализована через монады, что позволяет работать с асинхронностью в чисто функциональном стиле.
  • В браузере промисы могут быть использованы для работы с Web Workers, Service Workers и другими API, которые требуют асинхронного взаимодействия.

Источники

  • Спецификация ECMAScript 6 (ES2015), раздел «Promise Objects».
  • Спецификация Promises/A+ (promisesaplus.com).
  • Документация MDN Web Docs: «Promise».
  • Документация Node.js: «fs/promises».
  • Книга «You Don’t Know JS: Async & Performance» (Кайл Симпсон).
  • Статья «JavaScript Promises: An Introduction» (Google Developers).

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →