Эта статья является четвертой частью серии из четырех частей, посвященных асинхронному Javascript. Я настоятельно рекомендую вам изучить Часть 1, Часть 2 и Часть 3, чтобы извлечь максимальную пользу из этой статьи.

В этой статье мы рассмотрим интересную функцию Javascript, которая была введена в ES6 для эффективного выполнения асинхронного кода. До ES6 для запуска асинхронного кода (например, сетевого запроса) мы использовали функции обратного вызова. Но у этого подхода было много недостатков (включая ад обратных вызовов), которые приводили к проблемам с читабельностью кода, обработкой ошибок и отладкой. Чтобы решить эти проблемы, был представлен новый объект Javascript под названием Promise.

Обещать

Обещание - это особый тип объекта Javascript, который действует как заполнитель для возможного завершения или сбоя асинхронной операции. Он позволяет вам прикреплять к нему «обработчиков», которые обрабатывают значение успеха или причину неудачи, когда они поступают на более позднем этапе. Это позволяет нам вызывать асинхронные функции, как если бы они были синхронными, и сохранять их в прокси-объекте, который «обещает» вернуть результат на более позднем этапе. Попробуем лучше понять это на примере.

Базовый синтаксис обещания приведен выше. Обещание создается с помощью переданной в него функции, которая называется функцией исполнителя. Функция-исполнитель содержит асинхронный код, который вы хотите запустить. Функция содержит два аргумента: resolve и reject. Это функции обратного вызова по умолчанию, предоставляемые Javascript. Функция исполнителя запускается, как только создается обещание. Как только код этой функции завершится, нам нужно вызвать любую из функций обратного вызова:

  • resolve (value): вызов этой функции указывает на успешное состояние, где ‘value’ является значением, возвращаемым в результате успешного завершения функции-исполнителя.
  • reject (error): вызов этой функции указывает на сбой или ошибку, при этом значение ‘error’ является объектом Error, указывающим подробности ошибки. ‘error’ не обязательно должен быть Error объектом, но настоятельно рекомендуется.

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

  • состояние: изначально установлено значение «ожидает рассмотрения». Изменяется либо на «выполнено», если вызывается разрешение, либо на «отклонено», если вызывается отклонение.
  • результат: изначально установлено на undefined. Изменяет «значение», если вызывается resolve (value), или «error», если вызывается reject (error).

Давайте посмотрим, как работают вышеуказанные функции, на простом примере.

Приведенный выше код создает обещание сгенерировать случайное число от 1 до 10 и проверить, четное ли оно. Мы использовали setTimeout, чтобы реализовать задержку в 1 секунду. Когда объект обещания создается, его внутренним свойствам устанавливаются значения по умолчанию.

state: "pending"
result: undefined

Предположим, что randomNumber, сгенерированный в строке 2, является четным числом, например 4. В этом случае выполняется код в строке 5 и вызывается функция обратного вызова resolve со значением 4 в качестве аргумента. Это переводит объект обещания в состояние «выполнено». Это аналогично заявлению о том, что задача функции-исполнителя вернула результат «успех». Теперь свойства объекта обещания

state: "fulfilled"
result: 4

Если сгенерированное randomNumber было нечетным числом, например 7, то выполняется код в строке 7 и вызывается функция обратного вызова reject с объектом Error в качестве аргумента. Это переводит объект обещания в состояние «отклонено». Теперь свойства объекта обещания

state: "rejected"
result: Error("Not an even number");

Обратите внимание, что в обещании функция-исполнитель может вызывать только resolve или reject один раз. Любые последующие вызовы resolve или reject после первого игнорируются. Это потому, что обещание должно иметь единственный результат - успех или неудачу. Более того, и resolve, и reject принимают только один (или ноль) аргумент. Дополнительные аргументы игнорируются.

Важно отметить, что когда создается объект обещания, он не сохраняет сразу вывод асинхронной операции. Результат (который может быть либо значением успеха, переданным функцией resolve, либо значением ошибки, переданным функцией reject) получается только позже. Этот вывод сохраняется в 'result', который является внутренним свойством Promise и не может быть доступен напрямую. Чтобы получить результат, мы присоединяем к обещанию специальные функции-обработчики, которые мы обсудим ниже.

затем лови и наконец

Обещания имеют три важные функции или «обработчики», которые могут быть прикреплены к ним, что позволяет нам получать или «потреблять» их выходные данные. Первый - обработчик then. Базовый синтаксис then следующий.

Обработчик then принимает до двух функций обратного вызова в качестве аргументов. Первый обратный вызов выполняется, если resolve был вызван в функции исполнителя. Второй обратный вызов выполняется, если reject был вызван в функции исполнителя. Например, в следующем обещании функция resolve была вызвана в функции исполнителя.

Следовательно, был выполнен только первый обратный вызов, а второй был проигнорирован.

В случае вызова функции reject,

Первый обратный вызов был проигнорирован, а второй обратный вызов был выполнен.

У нас также могут быть отдельные обработчики для обработки результатов resolve и reject. Здесь в игру вступает обработчик catch. Он принимает в качестве аргумента только одну функцию обратного вызова и выполняется, если обещание было отклонено.

Третий доступный обработчик - finally. Это работает аналогично тому, как final работает в обычном сценарии try-catch. Обработчик finally не принимает аргументов и всегда выполняется, если он прикреплен к обещанию, независимо от того, было ли обещание выполнено или отклонено.

Ранее в этой статье мы упоминали, что одной из причин, по которой были введены обещания, было преодоление ада обратных вызовов. Особенность обещаний, которая позволяет достичь этого, - это способность цепочки. Обработчики обещания, а именно then, catch и finally, все возвращают обещание. Следовательно, мы можем использовать эти обработчики, чтобы «связать» несколько обещаний. Давайте посмотрим на простой пример.

В приведенном выше примере мы создали простое обещание, которое разрешается со значением 10. Затем мы потребляем этот результат с помощью нашей первой then функции в строке 5. Эта функция выводит значение '10' в консоль, а затем возвращает значение. 10 * 2 = 20. Из-за этого обещание, возвращаемое этой then функцией, разрешается со значением 20. Следовательно, в строке 9, когда вызывается функция then, ее результат равен 20. Этот результат 20 печатается. на консоль, с последующим возвратом 20 + 5 = 25. Опять же, обещание, возвращаемое текущей функцией then, поэтому разрешается со значением 25. Повторяя это, мы можем связать любое количество обещаний с существующим обещанием. Для получения дополнительной информации о цепочках вы можете найти этот документ на MDN.

Теперь, когда мы рассмотрели обещания, вам может быть интересно, как они вписываются в порядок выполнения. Попадают ли обработчики обещаний (then, catch и finally) в очередь обратного вызова, поскольку они асинхронны? Ответ - нет.

На самом деле они добавляются в что-то, что называется очередью микрозадач. Эта очередь была добавлена ​​в ES6 специально для обработки обещаний (и некоторых других типов асинхронных функций, таких как await). Итак, всякий раз, когда обещание готово (т. Е. Его функция-исполнитель завершила работу), все обработчики then, catch и finally обещания добавляются в очередь микрозадач.

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

Для получения дополнительной информации о Promises вы можете найти этот документ на MDN.

На этом я завершаю серию статей об асинхронном Javascript. Не стесняйтесь оставлять ответ на любые вопросы или предложения!