[Перевод] Промисы на примере бургер-вечеринки
Это перевод статьи, которую Марико Косака написала в качестве альтернативного введения в промисы 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-функция.
Добавляем обработчики промисов
Похоже, сигнализатор зазвонил, а значит, нужно подойти к прилавку и попросить свой заказ. Далее возможны два варианта развития событий.
- Заказ готов (промис выполнен). Ура! Ваш заказ готов и повар вручает вам свежий, вкусно пахнущий бургер. Промис выполнен!
- Заказ не готов (промис отклонен). Похоже, на кухне кончились котлеты, поэтому промис бургера не был выполнен. Получите компенсацию!
Вот как можно приготовиться к обеим ситуациям.
.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). Если вы невероятно голодны, то можете одновременно заказать бургер, чизбургер и хот-дог, но взять то, что первое поступит с кухни. Но в таком случае, если, к примеру, кухня закрылась или перестала почему-то работать, и первым делом отклоняет промис бургера, то и весь промис будет отклонен.
Также можете ещё почитать про промисы:
- promise-cookbook
- JavaScript Promises: an Introduction