Здравствуйте. Изучая JavaScript (да и в принципе любую другую технологию), всегда возникают разнообразные вопросы, главный из которых: «А почему это работает таким образом и не иначе?» И очень важно в этот момент не просто найти ответ на вопрос, но и полученное объяснение встроить в единую систему уже полученных знаний. Иначе осиротевшую информацию придется зазубрить или забыть.
  Совместное изучение чего-либо очень помогает искать ответы. Когда ученик / напарник задает вопрос про то, как понимать фразу — «… в следующий по цепочке промис „проваливается“ результат предыдущего… » — невольно задумываешься… Вот ведь какое странное дело. А ведь лучше уже не скажешь, неужели и это не понятно? Смотришь в чистые, немного наивные, глаза товарища и понимаешь — необходимо сказать как-то еще. Желательно так, чтобы даже запоминать не пришлось. Чтобы просто новая информация органично вписалась в уже существующие мысли человека.
  Не буду описывать, что мы пробовали, читали, смотрели. В итоге заинтересовались спецификацией ECMAScript. Как её читать и понимать — это отдельный разговор (может даже отдельный пост). Но то, как там описаны промисы и их поведение, впервые дало нам целостностное и логически связное понимание данной темы. Чем и хочется поделиться с вами. 
  Данная статья ориентирована на новичков. Здесь будут обсуждаться промисы с точки зрения спецификации ECMAScript. Знаю, звучит странно, но как есть.
 Уже не раз было мною замечено, что качественное обучение программированию должно состоять из 2 частей. Это — философское осмысление идеи, а уже затем её техническая реализация. То есть обычная человеческая логика, которой ученик руководствуется при принятии каких-либо решений, сильно облегчает понимание технической реализации этого решения. Поэтому начнем мы с того, что такое обещание в жизни, и как мы к нему относимся? А затем посмотрим: как примеры обещаний будут реализованы в коде. Рассмотрим следующие рисунки (рис. 1, 2, 3).
  Мы видим, что само понятие обещания стоит на 3-х китах. 1) Было ли выполнено обещание вообще? 2) Какую дополнительную информацию мы можем извлечь после выполнения или отказа обещания? 3) Какие последствия наступят в случае положительного или отрицательного исхода нашего обещания?
  Технически же обещание — это обыкновенная сущность, выраженная через такой тип данных, как объект. У этой сущности есть имя / класс Promise. Объекты, рожденные от этого класса, имеют в цепочке своих прототипов Promise.prototype. И данная сущность должна быть как-то связана со всей той «информацией из жизни», которую мы рассмотрели выше. Спецификация ECMAScript эту информацию закладывает в промис еще на уровне, который ниже по абстракции, чем сам JavaScript. Например, на уровень С++. Соответственно в объекте промиса есть место и под статус, и под результат, и под последствия обещания. Взгляните на то, из чего состоит обещание по по версии ECMAScript (рис. 4).
  Какими новыми красками заиграла фраза «обещать — не значит жениться» с точки зрения программиста? 1) [[PromiseState]]. Кто-то не женился. 2) [[PromiseResult]]. Потому что ему не хватило денег на свадьбу. 3) [[PromiseRejectReactions]]. Как следствие — у него появилось куча свободного времени, которое он потратил на саморазвитие 4) [[PromiseFulfillReactions]]. А зачем человеку план B, когда он уже выбрал план A?
  Да, есть и пятое поле [[PromiseIsHandled]]. Оно не очень для нас, людей, важное, и оперировать им в дальнейшем уже не будем. Вкратце: там хранится сигнал интерпретатору о том, был ли обработан отказ / reject обещания программистом или нет. Если нет, то необработанный reject обещания интерпретируется движком JS как ошибка. Для нетерпеливых: если программист не повесил вторым аргументом через Promise.prototype.then() функцию-callback-обработчик reject-а состояния промиса — то возникшее состояние «rejected» promise объекта покажет вам красную ошибку в консоли разработчика.
  Обратили внимание, что поля объекта promise заключены в «[[» и «]]»? Этим подчеркивается, что доступ к данной информации у программиста JS напрямую не имеется. Только через специальные средства / команды / API, как, например, команду Promise.prototype.then(). Если же у вас есть непреодолимое желание управлять «этой кухней» напрямую, то добро пожаловать в клуб разработчиков стандартов спецификации EcmaScript. 
  Небольшое замечание в конце данной подглавы. Если в жизни у нас могут быть обещания выполнены частично, то в EcmaScript — нет. То есть, если человек обещал отдать миллион, а отдал 950 тысяч, то в жизни, может, он и надежный партнер, но для для JavaScript такой должник будет занесен в черный список через [[PromiseState]] === «rejected». Promise объект меняет свое состояние однозначно и всего лишь один раз. Как это технически реализуется — немного позже.
  Итак, мы выяснили, что promise — это сущность, которая технически представляет собой JS объект с особыми скрытыми внутренними полями, которые в свою очередь обеспечивают философское наполнение смыслом слова «обещание». 
  Когда новичок первый раз создает объект promise, то его ожидает следующая картина (рис. 5).
  Что пошло не так, и почему ошибка — стандартный вопрос. При ответе на него лучше снова привести какую-то аналогию из жизни. Например, мало кто любит «пустозвонов» вокруг нас: которые только обещают, но ничего не делают по выполнению своих заявлений (политика не в счет). Мы намного лучше относимся к тем людям, которые после своего обещания имеют план и предпринимают сразу же какие-то действия для достижения обещанного результата.
  Так и философия ECMAScript подразумевает, что если вы создаете обещание, то сразу же укажите, как вы его будете выполнять. Свой план действий программисту необходимо оформить в виде параметра-функции, которую передадите в конструктор Promise. Следующий эксперимент выглядит так (рис. 6). 
 Из подписи к рисунку мы видим, что функция (параметр конструктора Promise) имеет собственное название — executor. Её задача — начать выполнение обещания и, желательно, привести его к какому-то логическому завершению. И если программист может писать какой угодно код в executor-е, то как программисту просигнализировать JS-у, что все — работа сделана — можно идти и смотреть результаты обещания?
  Маркеры или сигналы, которые помогут программисту сообщить, что обещание завершено, передаются автоматически в параметры executor-a в виде аргументов, специально сформированных JavaScript-ом. Эти параметры можно называть как угодно, но чаще всего вы встретите их под такими именами, как res и rej. В спецификации ECMAScript их полное название — resolve function и reject function. Эти маркеры-функции имеют свои особенности, которые рассмотрим чуть ниже.
  Для осознания новой информации новичку предлагается самостоятельно закодировать следующее утверждение: «Обещаю, что смогу разделить одно число на другое и выдать ответ, если только делитель не ноль». Вот как будет выглядеть приблизительно такой код (рис. 7).
 Теперь можно проанализировать полученный результат. Мы видим, что уже второй раз консоль браузера показывает объект промис в интересном виде. А именно: указаны 2 дополнительных поля в двойных квадратных скобках. Можно спокойно провести аналогию между [[PromiseState]] и [[PromiseStatus]], fulfilled и resolved, [[PromiseValue]] и [[PromiseResult]]. Да, браузер сам пытается подсказать программисту о наличии и значении внутренних полей promise объекта. Также мы видим воедино связанную систему объекта promise, функции executor, специальных функций-callback-маркеров res и rej.
  Чтобы ученик / напарник раскрепостился еще больше в этом материале, ему предлагается следующий код (рис. 8). Необходимо его проанализировать и ответить на следующие вопросы.
  Отработает ли код? Где здесь функция executor и какое она имеет имя? Подходящее ли в этом коде название «wantToDivide»? Что возвращает после себя функция bind? Почему в функцию bind аргументы передаются только на втором и третьем месте? Куда исчезли специальные функции resolve function и reject function? Каким образом необходимые вводные данные number1 и number2 попали в «план выполнения обещания»? Сколько элементов в псевдомассиве «arguments»? Можно ли по памяти восстановить то, как будет выглядеть ответ в консоли браузера?
  Читателю предлагается самому подумать над ответами на вопросы. А также 
поэкспериментировать в коде. Благо код небольшой и сама идея задачи — простая. Да, тут есть вопросы как на промисы, так и на общие знания JavaScript. Что поделать, везде нас поджидают неожиданности, которые не дают нам расслабиться. Как только вам станет все понятно — можно двигаться дальше.
  Итак, попили кофейку — двигаемся дальше. Рассмотрим поподробнее специальные функции resolve function и reject function, автоматически формируемые JavaScript-ом для перевода promise объекта в состояние fulfilled или rejected, символизирующее окончание выполнения обещания.
  Попробуем для начала взглянуть на них просто в консоли разработчика (рис 9). 
 Мы видим, что resolve function представляет собой функцию, которая принимает один аргумент (свойство length === 1). А её прототипом является Function.prototype. 
 Хорошо, продолжим эксперименты. А что будет, если ссылку на resolve / reject function вынесем из executor-а во внешнюю область видимости? Не поломается ли чего (рис. 10)?
 Ничего необычного. Функции как подвид объекта в JavaScript передаются по ссылке. Все отработало так, как мы и ожидали. В переменную из замыкания outerRes попала ссылка на нашу resolving function res. И мы воспользовались её функциональностью, чтобы перевести промис в состояние fulfilled снаружи самого executor-а. Следующий слегка модифицированный пример показывает ту же самую мысль, поэтому посмотрите на код и подумайте, в каком состоянии и с каким значением будут находиться myPromise1 и myPromise2 (рис.11). Потом можете проверить ваши предположения под спойлером. А теперь можно задуматься над одним интересным вопросом. А как resolve / reject function всегда точно знает какой promise объект переводить в необходимое состояние? Обратимся к алгоритму в спецификации, где описано, как эти функции создаются (рис. 13).
 Важные моменты, на которые надо обратить внимание:
 Алгоритмы, которые используют данную информацию из скрытых полей, сейчас не будем рассматривать, так как они многословные и более сложные. К ним еще надо подготовиться как в теоретическом, так и в моральном плане. Пока что просто могу подтвердить вашу мысль: «Ух, как просто, оказывается. Наверное, при каждом разрешении / резолвинге promise объекта будет проверяться „объектный“ флаг { [[Value]]: false }. И если он установлен в true, останавливаем процесс перевода promise в другое состояние простым return-ом». Да — это происходит именно так. Думается, правильно ответить на следующий вопрос вы сможете без проблем. Какой будет результат в консоли разработчика (рис. 15)?
 Рассмотрим тот завораживающий момент, когда на свет рождается он — полноценный promise объект (рис. 16). 
 Никаких сложных вопросов при его просмотре возникать уже не должно:  Не знаю, стало ли для вас открытием, что алгоритм executor функции выполняется здесь и сейчас, в обычном синхронном режиме, еще до того, как что-то будет записано в переменную слева от конструктора Promise. Но в свое время для меня это стало откровением.
 Раз уж затронули тему синхронности и асинхронности, то вот вам следующий код «на подумать» или для экспериментов. Вопрос: просмотрев некое творение программиста Димы, сможете ли вы ответить, какой смысл игры закодирован ниже?
 Конечно же, это эмуляция броска игрального кубика. Сможет пользователь угадать выпавшее число или нет? Посмотрите как органично асинхронный setTimeout встраивается в синхронный executor — в наш план бросить кубик и узнать выпавшее число. Как можно уже по-особому интерпретировать результаты в консоли разработчика (рис. 17)? 
 Если мы попробуем посмотреть промис до того времени, как кубик остановится (3000 мс указано в коде), то мы увидим, что промис до сих пор находится в состоянии ожидания: игра не завершилась, кубик еще не остановился, выпавшего числа нет. Если же мы попробуем посмотреть promise объект после остановки кубика, то мы увидим вполне конкретную информацию: выиграл ли пользователь (угадав число), или проиграл и почему (какое число выпало на самом деле).
 Если вас заинтересовал данный пример, или вы хотите угадать выпавшее число подброшенного кубика — можете скопировать код и провести свои эксперименты. Смелей!
 Как вы могли заметить на рисунке 14, последствия разрешения / резолвинга promise объекта подписаны как «+реакция» и «-реакция». Официальный термин для этих слов из ECMAScript спецификации — promise reaction. Предполагается, что в следующих статьях эта тема будет рассмотрена подробно. Пока что ограничимся общим представлением того, что такое promise reaction, чтобы можно было правильно ассоциировать этот термин с философским смыслом этого слова и его техническим исполнением.
 Как мы помним, у обещания могут быть последствия, а могут и не быть. Что же такое последствие? Это действие, которое произойдет некоторым временем позже: после того как обещание исполнится. А раз это действие, то последствие может быть выражено обычной JavaScript функцией. Одни функции исполнятся в случае успешного резолвинга промиса (+реакция); другие функции — в случае, когда промис перейдет в состояние rejected (-реакция). Технически эти функции (последствия) передаются аргументами при вызове метода Promise.prototype.then().
 Таким образом важной частью promise reaction является асинхронное действие, выполняемое когда-то в будущем. Есть и вторая важная составляющая часть promise reaction — это новосозданный промис, возвращаемый после выполнения команды Promise.prototype.then(). Это связано с тем, что последствия влияют на другие обещания. Например, есть обещание купить машину, но только после того, как будет выполнено обещание по зарабатыванию определенной суммы денег. Выполнили одно обещание — отработало последствие — теперь можно выполнить второе. 
 По факту promise reaction связывает обещания между собой в каком-то временном интервале. Важно помнить тот момент, что реакции обрабатываются в автоматическом режиме. Вызовы функций — последствий разрешения промиса — осуществляются движком JS, не программистом (рис. 18). И, так как реакции тесно связаны с самими promise объектами (обещаниями), логично предположить, что алгоритмы promise reaction используют их внутренние поля в своей логике. И лучше знать о всех этих нюансах, чтобы уметь осознанно контролировать асинхронную логику, построенную на обещаниях.
 1) Мы познакомились с обещаниями в JavaScript, их философией и техническим исполнением. Реализовано все это с помощью специальных внутренний полей promise объекта: [[PromiseState]], [[PromiseValue]], [[PromiseFulFillReactions]], [[PromiseRejectReactions]].
 2) Программисту дается возможность выполнить заявленное им обещание через функцию executor, передаваемую аргументом в конструктор Promise.
 3) Границы выполненного или невыполненного обещания определяются специальными маркерами-функциями resolve function и reject function, часто в коде именуемыми res и rej. Данные функции создаются автоматически JavaScript-ом и передаются аргументами в executor.
 4) resolve function и reject function всегда имеют связанный с ними promise объект, а также общее специальное поле { [[Value]]: false }, которое обеспечивает разрешение обещания только один раз.
 5) [[PromiseFulFillReactions]] и [[PromiseRejectReactions]] являются внутренними полями promise объекта, которые хранят последствия разрешения обещания, важной частью которых являются пользовательские асинхронные функции, задаваемые через метод Promise.prototype.then() promise объекта.
 Данная статья подготовлена как конспект видеозанятия группы InSimpleWords. Там достаточно подобных «видеозанятий» и есть еще материал для конспектирования. Другой вопрос — интересно ли будет участникам сообщества читать уже какую по счету статью про промисы? Ждем ваших комментариев.