JavaScript выполняет код последовательно сверху вниз. Иногда (что на самом деле случается часто) мы хотим запустить код после завершения выполнения функции или возникновения события, что означает запуск кода асинхронным способом.
Поскольку JavaScript является синхронизированным языком, нам нужно найти способ запускать его асинхронно.
Для этого мы используем функции обратного вызова. функция обратного вызова дает нам возможность выполнять функцию/событие/действие асинхронно.

Но подождите, есть проблема
Хотя функции обратного вызова — это способ решения проблемы синхронности, функции обратного вызова имеют некоторые недостатки, такие как пирамида гибели (или ад обратных вызовов).

Обещание спасения

Обещания не сразу стали популярными. На самом деле, когда сообщество начало искать способы обработки асинхронного поведения, были выбраны функции обратного вызова. Только когда jQuery начал реализовывать Promise на своей кодовой базе (которая стала популярной и широко используемой), Promise начал приобретать репутацию.

Ранние реализации промисов начали появляться еще в 1980-х годах. Использование слова обещание было придумано Барбарой Лисков и Любой Шрирой в 1988 году.

Обещание – это объект, который может создать одно значение в будущем. Он имеет 3 различных состояния:

  • Выполнено:resolve()
  • Отклонено: reject()
  • Ожидание: еще не выполнено или отклонено — ожидающее обещание может быть либо выполнено со значением, либо отклонено с указанием причины (ошибка).

Конструктор обещаний

Функция, переданная new Promise, называется исполнителем . Его аргументы resolve и reject являются обратными вызовами, предоставляемыми самим JavaScript. Наш код находится только внутри экзекьютора.
экзекьютор запускается автоматически и пытается выполнить задание. По завершении попытки он вызывает resolve, если она была успешной, или reject, если произошла ошибка.

Объект promise, возвращаемый конструктором new Promise, имеет следующие внутренние свойства:

  • state — сначала "pending", затем изменяется либо на "fulfilled" при вызове resolve, либо на "rejected" при вызове reject.
  • result — изначально undefined, затем изменяется на value при вызове resolve(value) или error при вызове reject(error).

Успешная работа — выполненное обещание

При вызове конструктора Promise с new Promise() исполнитель вызывается автоматически и немедленно и получает два аргумента resolve и reject . Оба они предопределены движком JavaScript, поэтому нам не нужно их создавать. Мы должны вызвать только один из них, когда будем готовы.

После одной секунды «обработки» исполнитель вызывает resolve("done") для получения результата. Это изменяет состояние объекта promise:

Неудачная работа — отказ от обещания

На этот раз в задании произошла ошибка, поэтому мы отклонили промис и перевели объект промиса в состояние "rejected":

исполнитель должен выполнить задание (обычно то, что требует времени), а затем вызвать resolve или reject, чтобы изменить состояние соответствующего объекта обещания.

Свойства state и result объекта Promise являются внутренними. Мы не можем получить к ним прямой доступ. Для этого мы можем использовать методы .then/.catch/.finally.

тогда лови, наконец

Объект Promise служит связующим звеном между исполнителем и потребляющими функциями, которые получат результат или ошибку. Функции-потребители могут быть зарегистрированы (подписаны) с помощью методов .then, .catch и .finally.

тогда

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

Первый обратный вызов вызывается, когда обещание разрешено. Второй обратный вызов выполняется, когда обещание отклонено.

В приведенном выше примере мы генерируем случайное число и разрешаем/отклоняем обещание в зависимости от того, больше или меньше число 8 (чтобы каждый раз получать ошибку, мы можем изменить число на 0).
Затем с помощью В следующем примере мы записываем в консоль результат, если обещание было разрешено или отклонено.

Обещание может быть успешным (разрешенным) или неудачным (отклоненным) только один раз. Он не может быть успешным или неудачным дважды, а также не может переключаться с успешного на неудачный или наоборот.
Если промис был успешным или неудачным, а позже вы добавите обратный вызов успеха/неудачи (например, .then), правильным обратным вызовом будет звонил, хотя событие произошло раньше.

Если нас интересуют только успешные завершения, то мы можем предоставить только один аргумент функции для .then:

поймать

Если нас интересуют только ошибки, то мы можем использовать null в качестве первого аргумента: .then(null, errorHandlingFunction). Или мы можем использовать .catch(errorHandlingFunction), что точно так же:

наконец-то

Наконец, также примите обработчик функции.
finally — хороший обработчик для выполнения очистки или выполнения любых действий независимо от того, было ли обещание разрешено или отклонено.

Вызов .finally(f) похож на .then(f, f) в том смысле, что f всегда выполняется, когда обещание выполнено: будь то разрешение или отклонение, но с некоторыми отличиями.

  1. Обработчик finally не имеет аргументов. В finally мы не знаем, успешно ли выполнено обещание. Ничего страшного, так как наша задача обычно состоит в выполнении «общих» процедур финализации.
  2. Обработчик finally передает результаты и ошибки следующему обработчику.

В приведенном выше примере обещание будет выполнено, и в конце концов мы увидим на консоли «все хорошо!» (через 1 секунду). Но перед этим мы используем finally, и он будет вызываться перед методом разрешенного обработчика.
Он будет действовать так же, если обещание было отклонено.

Цепочка

Вы видели, как в последнем примере я связал .then с .finally . Это еще одно преимущество объекта Promise, и именно здесь промисы действительно начинают выделяться на фоне простых шаблонов обратного вызова.

Идея состоит в том, что результат передается через цепочку обработчиков .then.
Когда обработчик возвращает значение, оно становится результатом этого промиса, поэтому с ним вызывается следующий .then.

Вся цепочка работает, потому что вызов promise.then возвращает промис, так что мы можем вызвать для него следующий .then.

Имейте в виду, что технически мы также можем добавить много .then к одному промису.Но это не цепочка.

Здесь мы сделали всего несколько обработчиков одного промиса. Они не передают результат друг другу; вместо этого они обрабатывают его независимо.

Точно так же, как мы объединяем группу .then(), мы можем использовать .catch() для обнаружения ошибок во время цепочки.

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

мы создали 3 разные функции, каждая из которых возвращает Promise .
Теперь мы можем вызывать их так

Если в какой-то точке цепочки обещание будет отклонено, мы перехватываем ошибку и печатаем ее, и, как мы уже узнали, будет напечатано finally независимо от того, в каком состоянии будет состояние обещания.