Вы когда-нибудь задумывались, как Promise работает в JavaScript? Давайте реализуем один и посмотрим, сможем ли мы понять его немного лучше.
Что такое обещание?
Обещание — это объект, который может представлять асинхронную операцию.
Этот объект обещает вернуть результат этой асинхронной операции, будь то успех или неудача. Если эта операция никогда не завершится, она будет ждать до вечности или до тех пор, пока не будет собран мусор.
Что нам нужно, так это средство для сбора результата этой асинхронной операции из промиса, и именно здесь метод then()
выходит на первый план.
Затем
Объектом thenable является любой объект с then()
реализацией метода. Да, обещания можно использовать.
Из контекста Promise метод then()
используется для добавления обработчиков, которые в конечном итоге получат результат асинхронной операции, которую он представляет.
Promise .resolve( 'result' ) .then( successcallback, failurecallback );
Операция после завершения приводит к тому, что обещание сопоставляется с разрешенным value
или отклоненным error
.
Если операция завершается успешно, срабатывает successcallback( value )
, и разрешенный value
передается в качестве аргумента.
Если операция завершается с ошибкой, срабатывает failurecallback( error )
, и отклоненный error
передается в качестве аргумента.
Таким образом, Promise может иметь только три состояния для представления асинхронной операции, а именно:
- ожидание: ожидание завершения асинхронной операции.
- выполнено: операция успешно завершена.
- отклонено: операция не удалась.
Когда обещание выполняется или отклоняется, оно становится окончательным состоянием обещания, таким образом делая обещание выполненным долго и счастливо…
После того, как обещание выполнено, никакие дальнейшие обновления не могут быть выполнены ни для результата, ни для состояния обещания.
Конструктор обещаний:
Конструктор Promise создает для нас объект обещания:
new Promise( executor )
Он ожидает функцию executor
, которая связывает асинхронную активность с обещанием.
Эта функция executor
вызывается конструктором Promise с аргументами resolve
и reject
, которые являются методами, предоставляемыми конструктором Promise для выполнения следующих действий:
resolve( data )
: установите состояние обещания наfulfilled
и результат какdata
.reject( error )
: установите состояние обещания наrejected
и результат какerror
.
В зависимости от асинхронной активности функция executor
может затем вызвать метод resolve
или reject
для передачи результата промису.
Созданный Promise имеет внутренние свойства:
[[PromiseState]]
: текущее состояние промиса.[[PromiseResult]]
: содержит разрешенное значение или причину отказа.
Давайте посмотрим на пример:
Создание PromiseCopy:
Начнем с constructor
. Мы получаем executor
, и нам нужно передать методы resolve
и reject
, которые являются служебными функциями.
resolve
и reject
— это статические методы в Promise, которые используются для создания нового Promise, как показано ниже:
Когда эти методы передаются функции executor
внутри конструктора Promise, они ведут себя по-разному. Они относятся к экземпляру Promise bind
и возвращают undefined
.
Обладая вышеуказанными знаниями, давайте продолжим и создадим PromiseCopy:
Обещание.разрешить():
Promise.resolve( value )
получает ответ об успехе, который установлен как #result
промиса.
Но если это значение является доступным объектом, Promise.resolve()
не устанавливает это значение как #result
.
если вы передадите объект, который можно использовать, в
Promise.resolve()
, он будет продолжать разворачивать результат, рекурсивно вызываяPromise.resolve()
, пока не достигнет конечного значения.
Как только Promise.resolve()
получает окончательное значение, он обновляет #result
обещания и устанавливает состояние выполнено.
Как только обещание выполнено, мы можем отправлять обратные вызовы, с нетерпением ожидающие #result
. Давайте напишем метод resolve()
, помня о вышеизложенном:
Обещание.отклонить():
Метод reject( reason )
очень похож на метод resolve()
, за исключением того, что он не выполняет разворачивание тенаблей. Он идет прямо вперед и устанавливает причину ошибки в #result
и устанавливает #state
как отклоненный. Кроме того, отправляйте обратные вызовы:
then() и цепочка обещаний:
Промис then( successcallback, failurecallback )
используется для добавления обратных вызовов. Эти обратные вызовы хранятся в экземпляре промиса и запускаются после того, как промис установлен.
Promise.then()
также является компонуемым, что означает, что вы можете объединить вызовы then()
, и каждый метод then()
получает результат предыдущей асинхронной операции.
Это особенно полезно, чтобы избежать ада обратных вызовов, поскольку вы можете связать зависимые асинхронные задачи с помощью цепочки промисов.
then( successcallback, failurecallback )
всегда возвращает новый промис, который разрешается возвращаемым значением либоsuccesscallback
, либоfailurecallback
в зависимости от#state
текущего промиса.
Если successcallback
или failurecallback
возвращает промис, результат разворачивается методом resolve()
нового промиса перед выполнением следующего метода then()
в цепочке промисов.
Еще одна важная вещь: если эти обратные вызовы не передаются, текущий промис #state
и #result
перенаправляется на новый промис.
Давайте посмотрим на реализацию then()
:
Обратите внимание, что запись помещается в #handlers
, которая будет активирована, когда текущий промис будет установлен.
Обработчики
Handlers — это массив, который содержит обратные вызовы, прикрепленные с помощью метода then()
. Эти обработчики хранятся в ссылке и запускаются асинхронно после установки промиса.
Каждый обработчик содержит методы:
success()
: выполняется, когда обещание выполнено.fail()
: выполняется, когда обещание отклонено.
Мы можем создать метод #dispatchCallback()
, чтобы проверить, выполнено ли обещание, и в конечном итоге вызвать обработчики.
Каждый обработчик должен запускаться только один раз после того, как обещание установлено, и впоследствии должен быть удален из ссылки.
Поскольку обработка Promise выполняется в очереди микрозадач, мы можем использовать метод queueMicroTask
для асинхронного запуска обработчиков как микрозадачи.
У нас также будет закрытое свойство #queued
в качестве проверки, чтобы избежать дублирования триггеров:
Ошибка обещания журнала
Обещание, если отклонено, регистрирует общую ошибку, если для обработки ошибки не указан failurecallback
.
Хотя в цепочке промисов отклонено промис будет пересылаться handler
до тех пор, пока не достигнет промиса без #handlers
. По сути, это может быть наша проверка для запуска журнала ошибок, как это было сделано выше в #dispatchCallback()
.
ловить():
Мы также можем присоединить failurecallback
, используя метод catch()
. Однако нам также необходимо передать значение следующему методу then()
, если обещание выполнено.
Для этого мы можем повторно использовать метод then()
, а также передать undefined
как successcallback
. Довольно простое право:
окончательно():
finally( finallycallback )
реализуется независимо от того, выполняется или отклоняется Promise. Но это не так просто, как просто передать finallycallback
в then( finallycallback, finallycallback )
.
Особенность finally()
заключается в том, что #result
текущего промиса не передается finallycallback
. Эти #state
и #result
текущего промиса передаются новому промису, возвращенному finally()
.
Если finallycallback
является асинхронным, то есть если он возвращает Promise или thenable, следующий обратный вызов в цепочке Promise будет запущен только после завершения этой операции.
В некотором смысле, finallycallback
— это просто некоторая промежуточная задача, стоящая между цепочкой промисов, которая должна быть успешной.
Потому что, если в finallycallback
есть ошибка, новое обещание, возвращаемое finally()
, отклоняется с этой ошибкой.
Давайте посмотрим на реализацию:
Заключение:
Понимание того, как работает Promise, может быть чрезвычайно полезным при реализации асинхронных задач в JavaScript. Я надеюсь, что эта статья поможет вам создать свой собственный промис, а затем асинхронно дождаться его.
Вы также можете перейти по ссылке ниже, чтобы поиграть с PromiseCopy
: