Javascript Promises и обещания в целом не являются новой концепцией в мире Javascript, но они не были очень популярны, пока не получили официальную поддержку в ES6. Обещания Javascript сами по себе не сложны, однако многим людям трудно подойти к ним и погрузиться в них в самом начале. Итак, начнем?

Что такое обещание?

Как указано в Mozilla docs:

Обещание — это прокси для значения, которое не обязательно известно при создании обещания. Он позволяет связать обработчики с возможным значением успеха или причиной отказа асинхронного действия. Это позволяет асинхронным методам возвращать значения, подобные синхронным методам: вместо немедленного возврата конечного значения асинхронный метод возвращает обещание предоставить значение в какой-то момент в будущем.

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

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

Прежде чем мы зайдем слишком далеко вперед. Давайте сделаем шаг назад, чтобы понять разницу между синхронно и асинхронно в программировании:

Когда программа выполняется впервые, она объединяется с рядом задач.

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

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

Из-за этого, если вы хотите значительно упростить процесс работы с асинхронностью, именно здесь родились промисы и стали нативно поддерживаться в Javascript. Этот инструмент помогает нам управлять асинхронными задачами в Javascript.

Но прежде чем углубиться в детали работы Javascript Promises, давайте рассмотрим следующий сценарий:

Представьте, что вы обедаете в ресторане и заказываете комплексный обед с жареной курицей, рисом и салатом.

Вы когда-нибудь задумывались о том, как они готовят еду и подают вам ваш заказ? Нам нравится думать об этом так: есть три стороны, которые сотрудничают друг с другом, чтобы приготовить вам еду. И давайте предположим, что они работают в разных частях кухни.

Повар готовит курицу, су-шеф готовит салат, а третий человек готовит рис.

Поток или работу можно абстрагировать следующим образом:

  • Работа 1. Повар, жарящий курицу.
  1. Шаг 1. Достаньте сырую курицу из холодильника, чтобы она оттаяла.
  2. Шаг 2. Маринуем курицу с различными специями в течение часа
  3. Шаг 3. Обжарьте курицу на сковороде, пока сок не станет прозрачным, кожа не будет проколота ножом, и она будет выглядеть готовой к употреблению.
  • Работа 2: Су-шеф готовит салат:
  1. Шаг 1. Разбираемся, какие овощи будут в салате
  2. Шаг 2. Смешать овощи с майонезом или другой приправой или заправкой
  • Работа 3. Третий повар, который готовит рис.
  1. Шаг 1. Промыть рис
  2. Шаг 2. Приготовьте рис

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

Но процесс каждой работы не должен блокировать поток работы всей кухни. Результат текущего шага может повлиять на завершение следующего шага. При этом обжаривание курицы не должно блокировать работу всей кухни.

Например, чтобы замариновать курицу, ее нужно сначала разморозить. И вы должны замариновать курицу со специями, прежде чем жарить или обжаривать ее в масле.

Пока готовится курица, готовятся салат и рис.

Промисы Javascript управляют задачами почти так же, как кухня выполняет работу (жарка курицы, приготовление салата и приготовление риса). Это похоже на асинхронные задачи в программе.

Хватит о еде. Давайте применим эти концепции к реальной программе.

Что такое обещание в Javascript?

Когда программа запускается, асинхронной задаче требуется время для завершения ее выполнения (или сбоя), а затем она возвращает некоторый результат. Мы знаем, что оно произойдет в определенное время в будущем, но программа не может гарантировать, когда именно это событие произойдет.

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

В целом это не повлияет на выполнение или результат асинхронных задач. Вместо немедленного возврата окончательного значения асинхронный метод возвращает обещание предоставить значение в какой-то момент в будущем.

Программа должна заранее определить сценарии того, что делать, когда асинхронная задача завершается и когда она завершается с ошибкой.

Как работают промисы?

Промис в своей жизни будет находиться в одном из следующих состояний:

  • ожидание: исходное состояние, ни выполнено, ни отклонено. Это состояние, когда оно было создано с использованием нового конструктора Promise.
  • выполнено: означает, что операция завершена успешно.
  • отклонено: означает, что операция не удалась.

Синтаксис создания промисов:

var promise = new Promise( /* executor */ function(resolve, reject) {        // Do something, possibly an async task.              if (/* Everything turned out fine */) {           resolve(/* Maybe the result of the async tasks etc */);           // After this the state of this Promise becomes fulfilled.           // When resolve function is called, it means that this Promise notified            // the completion of the asynchronous task being moderated.       }       else {           reject(Error("Some error message"));           // After this, the state of this Promise becomes rejected.           // When resolve function is called, it means that this Promise notified            // the end of the asynchronous task with an error.       }   } );

Созданный объект Promise оборачивает асинхронное выполнение, чтобы модерировать то, что происходит внутри исполнителя.

Как только оно (обещание) объявлено, исполнитель вызывается немедленно. Исполнитель вызывается еще до того, как конструктор Promise вернет готовый объект.

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

.тогда() метод

Обещание объекта этого типа Promise имеет метод then(). Этот метод принимает в свои параметры два типа функций.

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

Другая — это функция, которая вызывается, если обещание отклонено.

Рассмотрим следующий пример. Обратите внимание, что в этой программе мы предполагаем, что 1 секунда означает 1 минуту. И мы используем функцию setTimeout() (своего рода асинхронную) для простой иллюстрации:

var thawChicken = tineToThaw_minutes => {       console.log("Chicken is being thawed...");          return new Promise((resolve, reject) => {       let isSomethingWrong = false;          setTimeout(() => {           if(!isSomethingWrong){           resolve("Thawed chicken"); // <= This will be the argument of the first function parameter of then method.           }else{           reject(Error("Something wrong happended!")); // <= This error object will be passed as the argument to the second function parameter of then method.           }          }, tineToThaw_minutes * 1000);       });   }      thawChicken(3 /* minutes */).then(       chicken => console.log("What we have after waiting? - " + chicken),       error   => console.log("Error message: " + error.message)   )

Ознакомиться с живым примером можно здесь.

Если вы измените isSomethingWrong на true в приведенном выше примере, вы увидите уведомление об ошибке.

.поймать() метод

Как вы можете видеть в приведенном выше примере, обещание имеет как успех, так и обработчик ошибок.

Но что произойдет, если обработчик успеха выдаст ошибку? Там нечего ловить, тогда ошибка проглатывается.

Вот где в игру вступает метод .catch(). Просто используйте его так:

thawChicken(3 /* minutes */).then(       chicken => console.log("What we have after waiting? - " + chicken),       error   => console.log("Error message: " + error.message)   ).catch(errorHander);

Мы рекомендуем завершать все цепочки промисов с помощью .catch()!

Правила обещаний

  • Когда Promise инициируется, его состояние — ожидание. Это обещание может быть изменено только на состояние решено или отклонено.
  • Выполнено или отклонено обещание выполнено, и оно не должно переходить в какое-либо другое состояние, даже если вы попытаетесь сбросить его.
new Promise((resolve, reject) => {     resolve('resolved!');     resolve('re-set resolved!');     reject('rejected!');   }).then(     mes => console.log(mes),     err => console.log(err)   );

Это выведет: «решено!»

  • Как только обещание выполнено, оно должно иметь значение (которое может быть неопределенным). Другими словами, это значение не должно меняться.

Цепочка

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

Преобразование значений

new Promise((resolve, reject) => {     resolve('message');   }).then(     mes => mes + ' to transform'   ).then(     transformedMessage => console.log(transformedMessage)   );

Вывод будет таким: «сообщение для преобразования».

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

Управление потоком асинхронных задач

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

var thawChicken = tineToThaw_minutes => {     console.log("Chicken is being thawed...");        return new Promise((resolve, reject) => {       let isSomethingWrong = false;          setTimeout(() => {         if (!isSomethingWrong) {           resolve("Thawed chicken"); // <= This will be the argument of the first function parameter of then method.             } else {           reject(Error("Something wrong happended!")); // <= This error object will be passed as the argument to the second function parameter of then method.             }          }, tineToThaw_minutes * 1000);     });   }    var marinateChicken = (chicken, spices) => {     console.log("Marinating chicken with spices...");        return new Promise((resolve, reject) => {       setTimeout(() => {         resolve("Marinated Chicken");       }, 5000);     });   }      thawChicken(3 /* minutes */ ).then(     chicken => {       console.log("What we have after waiting? - " + chicken);       let spices = ["Salt", "Sugar", "Pepper", "Garlic"];       return marinateChicken(chicken, spices);     },     error => console.log("Error message: " + error.message)   ).then(     marinatedChicken => console.log("After marinating we have: " + marinatedChicken); );

Посмотреть живой пример можно здесь.

Используя цепочку, код выглядит так естественно в лексическом виде.

Обещание.все

Поиск в Google объяснит, что::

Метод Promise.all(iterable) возвращает один промис, который разрешается, когда все промисы в аргументе iterable разрешаются или когда iterable аргумент не содержит обещаний. Он отвергает по причине первого обещания, которое отвергает.

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

Promise.all отклоняется, когда любое из переданных обещаний отклоняется, независимо от того, были ли разрешены другие обещания.

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

Первоначально опубликовано на сайте pangara.com 9 сентября 2018 г.