Это перевод статьи, которую Марико Косака написала в качестве альтернативного введения в промисы 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). Если вы невероятно голодны, то можете одновременно заказать бургер, чизбургер и хот-дог, но взять то, что первое поступит с кухни. Но в таком случае, если, к примеру, кухня закрылась или перестала почему-то работать, и первым делом отклоняет промис бургера, то и весь промис будет отклонен.Также можете ещё почитать про промисы:
Поделиться с друзьями
Комментарии (11)
comerc
02.03.2017 14:50+1Следующая прекрасная статья для дальнейшего погружения в промисы (но и она освещает не все вопросы).
Rad1calDreamer
02.03.2017 15:26-1Сначала не мог вспомнить, где я видел уже это. А твиттер у нее и правда интереснеый
Danik-ik
02.03.2017 22:21Что-то не так с примером про коктейль и бургер. Это никак не показывает чейнинг, ведь чейнинг подразумевает обязательную последовательность действий, которая в примере не отражена. К примеру, заказать — посидеть на лавочке — выкинуть фантик подошло бы лучше. Особенно, если к урне тоже очередь :)
monrostar
03.03.2017 10:37Для меня статья очень в нужное время появилась, как раз погружаюсь в эту рутину и легкое полезное чтиво очень приятный результат траты времени :)
sergyx
03.03.2017 10:37+11.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();
impwx
Статья легкая и отличный перевод, но сама метафора довольно далека от отечественной действительности. Из всех бургерных на моей памяти сигнальный брелок давали только в Shake Shack, а остальные высвечивают номер на табло или кричат его с кухни. Из push-уведомления ситуация превращается в регулярный pull :(
Quilin
Когда кричат с кухни — это вполне себе пуш, кажется =)
impwx
Как правило в бургерной довольно шумно, нотификация с кухни теряется и приходится самому делать регулярный pull с чеком — «а мой заказ еще не готов?».