Используйте обещания JavaScript как бариста
Искусство бариста и ремесло программиста могут показаться совершенно разными. Но подождите немного, и начнет проявляться удивительная гармония. Эта гармония становится особенно убедительной в асинхронном JavaScript — концепции столь же многослойной и сложной, как создание идеального латте.
Представьте, что вы стоите в очереди в своем любимом кафе. Запах свежемолотых бобов, тихий гул разговоров, бариста, работающая над очередью, полной заказов. Классический латте занимает особое место среди множества напитков, которые оживают под их умелыми руками. Это не просто напиток; это свидетельство тонкого баланса искусства и науки.
В этой части мы приоткрываем завесу над этой неожиданной синхронностью, распутывая нити, связывающие мир приготовления кофе с замысловатым гобеленом программирования. От точности приготовления порции эспрессо до утонченности дымящегося молока и, наконец, тщательного объединения этих элементов — мы погружаемся в то, как эти шаги эхом отдаются в коридорах программирования на JavaScript.
Приготовление латте
Приготовление порции эспрессо — это первый шаг, который требует извлечения растворимых соединений, ответственных за богатый вкус кофе, соблазнительный аромат, крепкое тело и эффект кофеина. Этот процесс начинается с измельчения кофейных зерен до мелкой текстуры — намного меньше, чем у капельного кофе. Затем эти грунты утрамбовываются, что позволяет проводить равномерную экстракцию по мере пропускания воды под высоким давлением. Эта техника, первоначально выполнявшаяся вручную путем нажатия на рычаг, является источником фразы «вытягивание выстрела».
Далее идет запаривание молока. Этот шаг требует утонченности и пристального внимания к деталям, нагревания молока до идеальной температуры при достижении нужного уровня аэрации. Цель? Бархатистая микропена. Эта кремообразная пена усиливает насыщенный вкус эспрессо и придает латте особую роскошную текстуру.
Наконец, эти тщательно подготовленные элементы объединяются. Влить в эспрессо вспененное молоко может любой желающий. Тем не менее, чтобы соединить эспрессо, молоко и микропену в нежнейшее наслаждение, нужно немного мастерства.
Эти шаги могут показаться простыми, но они представляют собой лишь часть роли бариста. Это требует сочетания навыков, понимания сложных процедур, точного расчета времени и способности совмещать несколько задач без ущерба для конечного продукта. Любопытно, что эти требования не так уж далеки от мира программирования. Подобно бариста, программист должен подходить к решению проблем с разных сторон, чтобы обеспечить эффективное и качественное решение.
Работайте как бариста, работайте асинхронно
Эта аналогия особенно актуальна при рассмотрении асинхронного программирования в JavaScript. Эта парадигма программирования позволяет разработчикам выполнять несколько операций одновременно, предлагая гибкость в определении того, какие задачи должны выполняться одновременно, а какие должны выполняться в определенном порядке. Это поразительно напоминает рабочий процесс бариста при приготовлении латте.
Promise в JavaScript — это встроенный объект, который обозначает конечный результат асинхронной задачи, независимо от того, успешна она или нет. Обещания предлагают две важные функции — разрешение и отклонение — которые определяют возвращаемое значение обещания. Когда операция выполнена успешно, вызывается функция «разрешить», тогда как «отклонение» сигнализирует об ошибке.
Чтобы провести базовую аналогию с рабочим процессом бариста, давайте рассмотрим некоторый псевдокод, использующий Promises.
Псевдокод обещаний
let steamMilk = () => { // Promise object is created. It is a placeholder for the eventual outcome of an operation. return new Promise((resolve, reject) => { let steamedMilk = steamMilkByHand(); if (steamedMilk) { // The 'resolve' function is called to signify a successful outcome // and the value it receives is the return value of the Promise. resolve(steamedMilk); } else { // The 'reject' function is called when the operation fails // with the reason for failure being its argument. reject("Milk steaming failed"); } }); } let pullEspressoShot = () => { // Promise object is created. It is a placeholder for the eventual outcome of an operation. return new Promise(async (resolve, reject) => { grindCoffeeBeans(); tampDown(); // consider pullEspresso() containing an api call // we must await the resolution of the api call // therefore, we use a Promise inside of a Promise let shot = await pullEspresso(); if (shot) { // The 'resolve' function is called to signify a successful outcome // and the value it receives is the fulfillment value of the promise. resolve(shot); } else { // The 'reject' function is called when the operation fails // with the reason for failure being its argument. reject("Shot pulling failed"); } }); };
Пример выше включает две функции: pullEspressoShot()
и steamMilk()
. Каждая функция возвращает обещание, представляющее действие бариста. Если действие выполнено успешно, обещание «разрешается» с результатом; если это не удается, оно «отклоняется» с ошибкой.
Сделать латте программно
Однако, поскольку для создания латте в нашем коде требуется два промиса, нам нужно организовать их поведение. Используя несколько промисов, разработчики диктуют многоэтапный процесс, например приготовление латте. Каждым шагом в процессе можно управлять независимо и параллельно, при этом результат каждого шага используется в следующей части операции. Это отражает рабочий процесс бариста, где приготовление эспрессо и пропаривание молока происходят одновременно, но на последнем этапе они объединяются.
Обещание.все
let makeLatte = () => { Promise.all([pullEspressoShot(), steamMilk()]) .then(values => { // The 'then' method is called when the promise is fulfilled // and it receives the fulfillment value. let latte = combine(values[0], values[1]); return latte; }) .catch(error => { // The 'catch' method is called when the promise is rejected // and it receives the reason for failure. console.log(`Error in latte preparation: ${error}`) }) .finally(() => { // The 'finally' method is called when the promise is settled // regardless of whether it was fulfilled or rejected. console.log("Latte Complete? Is the customer satisfied?"); }); } makeLatte();
В приведенном выше коде используется метод Promise.all, который возвращает один промис, который разрешается, когда разрешены все промисы. Это похоже на то, как бариста позволяет приготовить эспрессо, пока молоко готовится на пару, а затем, когда оба этапа будут удовлетворены, они объединяются и подаются покупателю.
Однако JavaScript предлагает другой способ управления этими асинхронными задачами — использование синтаксиса async/await. Он работает аналогично, но синтаксис отличается, и для многих он более читабелен.
АСИНХР./ОЖИДАНИЕ
// declare makeLatte() as an async function let makeLatte = async () => { try { // The async declaration allows us to 'await' the result of Promise(s) let values = await Promise.all([pullEspressoShot(), steamMilk()]); let latte = combineIngredients(values[0], values[1]); return latte; } catch (error) { // If the try block fails, the catch block executes console.log(`Error in latte preparation: ${error}`); } finally { // The finally block executes no matter the result of try or catch. console.log("Latte Complete? Is the customer satisfied?"); } } makeLatte();
В этой асинхронной/ожидающей версии кода «ожидание» используется в качестве кнопки «паузы», откладывая выполнение функции до разрешения промисов. Мы как бы говорим нашей программе: «Подожди здесь, пока я проверю что-то еще, и не двигайся вперед, пока я не вернусь к тебе». С другой стороны, Promise.all сродни прилежному бариста, выполняющему несколько задач одновременно. Эта функция не вернется, пока все промисы в массиве не будут разрешены, после чего она скомпилирует массив разрешенных значений. Однако, если хотя бы одно обещание не выполняет свои обязательства, «Promise.all» без колебаний отметит это, немедленно отклонив по причине первого невыполненного обещания и проигнорировав остальные, независимо от того, были ли они решены или нет.
В оживленной кофейне и требовательном проекте программирования редко существует универсальный метод для успешного выполнения задачи. Каждая ситуация требует глубокого понимания задачи, эффективного управления временем и стратегического выбора правильных инструментов или методов. Хороший бариста и опытный программист постоянно следят за этим тонким балансом. Их мастерство заключается не только в том, чтобы приготовить вкусный латте или отлаженную программу; речь идет о путешествии, которое они предпринимают — путешествии, полном проблем, открытий, улучшений и общих принципов.