Это перевод статьи, которую Марико Косака написала в качестве альтернативного введения в промисы JavaScript. Наброски иллюстраций она делала в своём блокноте во время чтения разных статей, посвящённых промисам. Если хотите изучить более подробно, в конце вы найдёте список полезных ссылок.

Недавно Марико участвовала в обсуждении того, как можно с помощью JavaScript сделать фичу, которая давала бы доступ к внешним данным (должна была быть асинхронной). Она сказала: «Ну, давайте используем fetch()… так что в коде… эээ...», и пока силилась вспомнить fetch API, собеседник сказал: «Будет возвращаться промис». По словам Марико, её мозг впал в ступор, и она сказала: «Честно говоря, не знаю, что ты имеешь в виду…»

Ей приходилось много раз писать код, основанный на промисах, но для полной картины нужные пазлы в её голове почему-то не соединились. Она поняла, что на самом деле не «въезжает» в суть.

Я даже описать не могу, насколько трудно объяснить это выражение: «Будет возвращаться промис»
Но это, наверное, потому, что я не понимаю, что такое «промис».
— Mariko Kosaka (@kosamari) 13 января, 2017

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

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

// асинхронная операция
function cookBurger (type) { ... }

// обычная операция
function makeMilkshake (type) { ... }

// функция заказа, которая возвращает промис
function order (type) {
  return new Promise(function(resolve, reject) {
    var burger = cookBurger(type)
    burger.ready = function (err, burger) {
      if (err) {
        return reject(Error('Error while cooking'))
      }
      return resolve(burger)
    }
  })
}

order('JakeBurger')
  .then( burger => {
    const milkshake = makeMilkshake('vanila')
    return { burger: burger, shake: milkshake }
  })
  .then( foodItems => {
    console.log('BURGER PARTY !', foodItems)
  })
  .catch( err => {
    console.log(err)
  })

Давайте организуем бургер-вечеринку


Добро пожаловать в Promise Square Park, где находится бургерная JakeShack. Её бургеры очень популярны, но количество кассовых аппаратов для оформления заказов ограничено, так что очередь из клиентов у прилавка всегда большая. Однако, на кухне работают отличные повара, способные одновременно готовить несколько заказов.

Если что, то прототипами стали Madison Square Park и ShakeShack в Нью-Йорке. Там очень вкусно кормят, и очередь всегда большая.

Промисификация действий


Чтобы как можно быстрее принимать заказы, JakeShack использует систему сигнализаторов. Когда покупатель оплачивает заказ, кассир выдаёт поднос и устройство-сигнализатор в обмен на платеж.



Поднос — это промис JakeShack клиенту, что на подносе окажется вкусный бургер, как только он будет готов, а сигнализатор показывает состояние заказа. Если он молчит, то заказ обрабатывается (pending) — повара на кухне заняты работой над вашим заказом. Когда экран засветится красным и включится звуковой сигнал, это будет означать, что заказ собран (settled).

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

Давайте взглянем на код. Когда вы вызываете функцию order, она «возвращает промис» (даёт вам поднос с сигнализатором). Возвращаемое значение (бургер) должно появиться на подносе, когда будет выполнен промис и вызвана callback-функция.



Добавляем обработчики промисов


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



  1. Заказ готов (промис выполнен). Ура! Ваш заказ готов и повар вручает вам свежий, вкусно пахнущий бургер. Промис выполнен!
  2. Заказ не готов (промис отклонен). Похоже, на кухне кончились котлеты, поэтому промис бургера не был выполнен. Получите компенсацию!

Вот как можно приготовиться к обеим ситуациям.



.then() принимает другую функцию в качестве второго аргумента, которая также может использоваться как обработчик reject'а. Для простоты здесь я использовала только .catch(). Если вам хочется больше узнать о том, как обрабатываются оба варианта, почитайте эту статью: https://developers.google.com/web/fundamentals/getting-started/primers/promises#error_handling.

Цепочка промисов (чейнинг)


Допустим, ваш заказ был готов, но вы решили, что для бургер-вечеринки вам ещё нужен молочный коктейль. Итак, вы встали в С-очередь (отдельная очередь за напитками, действительно используется в ShakeShack для более эффективного менеджмента заказов). Когда вы заказываете свой коктейль, кассир даёт вам другой поднос и другой сигнализатор. А поскольку молочный коктейль готовится очень быстро, кассир сразу же его и выдаёт, вместе с подносом. Не нужно ждать, когда зазвенит сигнализатор (он уже это делает!).



Теперь посмотрим, как работает наш код. Цепочка промисов — это просто добавление в код нового .then(). Значение, возвращаемое .then(), всегда является промисом. Просто запомните, что каждый .then() возвращает вам поднос и сигнализатор, а настоящее возвращаемое значение передаётся в виде аргумента в callback-функцию.



Теперь у вас есть бургер и молочный коктейль, вы готовы к БУРГЕР-ВЕЧЕРИНКЕ!

Дополнительные трюки!


У промисов есть ещё парочка методов, позволяющих выполнять клёвые трюки.

Promise.all() создаёт промис, который принимает в качестве аргумента массив промисов (элементов). Этот промис выполняется тогда, когда выполнены всего его элементы. Допустим, вы заказали 5 разных бургеров для своих друзей, но не хотите ходить и забирать их по одному все 5 раз, а тогда, когда они все будут готовы. В данном случае хорошее решение — Promise.all().

Promise.race() похож на Promise.all(), но при этом промис считается выполненным или отклонённым, как только будет выполнен или отклонён хотя бы один его элемент. Такое поведение позволяет эмулировать схему «пробуй и хватай» (try and grab). Если вы невероятно голодны, то можете одновременно заказать бургер, чизбургер и хот-дог, но взять то, что первое поступит с кухни. Но в таком случае, если, к примеру, кухня закрылась или перестала почему-то работать, и первым делом отклоняет промис бургера, то и весь промис будет отклонен.

Также можете ещё почитать про промисы:

Поделиться с друзьями
-->

Комментарии (11)


  1. impwx
    02.03.2017 13:23

    Статья легкая и отличный перевод, но сама метафора довольно далека от отечественной действительности. Из всех бургерных на моей памяти сигнальный брелок давали только в Shake Shack, а остальные высвечивают номер на табло или кричат его с кухни. Из push-уведомления ситуация превращается в регулярный pull :(


    1. Quilin
      02.03.2017 14:53
      +1

      Когда кричат с кухни — это вполне себе пуш, кажется =)


      1. impwx
        02.03.2017 14:56

        Как правило в бургерной довольно шумно, нотификация с кухни теряется и приходится самому делать регулярный pull с чеком — «а мой заказ еще не готов?».


  1. comerc
    02.03.2017 14:50
    +1

    Следующая прекрасная статья для дальнейшего погружения в промисы (но и она освещает не все вопросы).


  1. Rad1calDreamer
    02.03.2017 15:26
    -1

    Сначала не мог вспомнить, где я видел уже это. А твиттер у нее и правда интереснеый


  1. Danik-ik
    02.03.2017 22:21

    Что-то не так с примером про коктейль и бургер. Это никак не показывает чейнинг, ведь чейнинг подразумевает обязательную последовательность действий, которая в примере не отражена. К примеру, заказать — посидеть на лавочке — выкинуть фантик подошло бы лучше. Особенно, если к урне тоже очередь :)


  1. monrostar
    03.03.2017 10:37

    Для меня статья очень в нужное время появилась, как раз погружаюсь в эту рутину и легкое полезное чтиво очень приятный результат траты времени :)


  1. sergyx
    03.03.2017 10:37
    +1

    1.function order (type) {
    2.  return new Promise(function(resolve, reject) {
    3.    var burger = cookBurger(type)
    4.    burger.ready = function (err, burger) {
    5.     if (err) {
    6.        return reject(Error('Error while cooking'))
    7.      }
    8.      return resolve(burger)
    9.    }
    10.  })
    11.}
    

    Хоть к промисам это отношения и не имеет, но функция, назначенная в 4 строке, никогда не выполнится. Правильнее как-то так:
    var burger = new Burger(type);
    burger.ready = function (err, burger) {
        if (err) {
           return reject(Error('Error while cooking'))
         }
         return resolve(burger)
    };
    burger.cook();
    


  1. BassCreator
    03.03.2017 10:37

    Было бы круто, если бы подобный перевод статьи был на тему Obaservables.


  1. WebMonet
    03.03.2017 17:49

    А можно маленьких примеров того, зачем это нужно? В чем плюсы, преимущества?


    1. comerc
      03.03.2017 19:22

      Например обращение к внешнему API, когда присутствует задержка ответа.