[Перевод] Промисы на примере бургер-вечеринки

5c625645faa549d4ae5e9791a680e893.png

Это перевод статьи, которую Марико Косака написала в качестве альтернативного введения в промисы 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 использует систему сигнализаторов. Когда покупатель оплачивает заказ, кассир выдаёт поднос и устройство-сигнализатор в обмен на платеж.

41a8a81ff5004be9b927886fc6c9fb23.png

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

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

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

bfdf582119b84bdeb8d2c2b8ef61c8c9.png

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


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

c91154133d5a4e808a5708d74399cb7c.png

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

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

d6dcada78d2943a8b336f8f665e53e1b.png

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

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


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

a32a45bab5184d59aa5c3bcbb9530e0b.png

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

1dff87a614d54e149ce2ad1c47ffe168.png

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

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


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

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

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

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

  • promise-cookbook
  • JavaScript Promises: an Introduction

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

© Habrahabr.ru