<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Асинхронное программирование</title>
    <description/>
    <link>https://tproger.ru/tag/asinhronnoe-programmirovanie</link>
    <atom:link href="https://tproger.ru/tag/asinhronnoe-programmirovanie/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Wed, 23 Sep 2026 22:52:40 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Асинхронное программирование</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Async Rust: как уменьшить бинарник, пока rustc не оптимизирован</title>
      <link>https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan</link>
      <comments>https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгений Стребков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan</guid>
      <description><![CDATA[<p>Разбираем, почему async Rust генерирует лишние state machine, как это бьёт по embedded и WASM, и что можно сделать в коде прямо сейчас.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/async-rust-kak-umenwit-binarnik-poka-rustc-ne-optimizirovan">Async Rust: как уменьшить бинарник, пока rustc не оптимизирован</a>»</p>]]></description>
      <category><![CDATA[Многопоточность]]></category>
      <category><![CDATA[Низкоуровневое программирование]]></category>
      <category><![CDATA[Оптимизация]]></category>
      <category><![CDATA[Компиляторы]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 29 Jun 2026 10:21:42 GMT</pubDate>
      <content:encoded><![CDATA[<p>Асинхронный Rust обещал нам zero-cost abstraction: пишете почти как на обычном синхронном языке, а runtime сам разбирается с конкурентностью. На сервере с десятками гигабайт RAM это обещание обычно сбывается. Но стоит перейти к embedded-прошивке на 256 КБ flash или к WebAssembly-модулю, который гонится за каждым килобайтом, — и картина меняется. Функция с двумя await-точками превращается в state machine из 360 строк MIR, тогда как синхронный аналог укладывается в 23. Почему так происходит и что с этим делать уже сегодня?</p><p>Тема получила новый импульс после серии публикаций Диона Доктера (Dion Dokter) из голландской компании Tweede golf. Во второй части он заглядывает внутрь rustc и утверждает: async Rust так и не вышел из состояния MVP. Я не буду дословно переводить его рассуждения, а переосмыслю их с упором на практику: как понять, платите ли вы за async-удобство лишними байтами, и как снизить налог, пока компилятор не научился делать это сам.</p><ul><li>Асинхронный Rust компилируется в state machine: на каждую await-точку приходится отдельное состояние плюс служебные Unresumed, Returned и Panicked.</li><li>Простая функция с двумя await-точками даёт 360 строк MIR против 23 у синхронного кода. Часть излишков LLVM убирает, но не все, особенно при оптимизации по размеру.</li><li>Компилятор не схлопывает идентичные состояния, не инлайнит futures через await, генерирует state machine даже для async-блоков без await и оставляет panic-путь в состоянии Returned.</li><li>В release-сборках замена panic в Returned на возврат Pending может сократить прошивку на 2–5%. Убирание state machine у пустых async-блоков — ещё около 0,2%.</li><li>Пока rustc не исправлен, можно вручную: возвращать impl Future из «прозрачных» функций, использовать std::future::ready, схлопывать await-точки через match и передавать большие данные по ссылке.</li></ul><h2>Почему async-функция — это всегда state machine</h2><p>Когда вы пишете async fn, компилятор не просто оборачивает тело в callback. Он строит конечный автомат: enum, где каждый вариант — состояние между await-точками. Это называется coroutine desugaring и происходит на уровне MIR (Mid-level IR) — до того, как код попадает в LLVM.</p><p>Рассмотрим минимальный пример. Есть две функции, каждая из которых просто возвращает число, и третья складывает результаты:</p><p>У bar две await-точки, поэтому минимум два пользовательских состояния. Но если сдампить MIR после прохода coroutine_resume, картина шире:</p><p>Три служебных состояния добавляются всегда. Они нужны, чтобы соблюсти контракт Future::poll: опрос завершённой future не должен приводить к неопределённому поведению. Поэтому после первого Ready future переходит в Returned, а повторный poll паникует. Аналогично Panicked блокирует future после пойманного panic — похоже на poisoning мьютекса.</p><p>Логика корректная, но цена высокая: bar порождает 360 строк MIR, а эквивалентный синхронный код — 23. В 15 с лишним раз больше. LLVM часть этого выбросит, но на opt-level = "z" или в толстых async-графах вызовов он быстро сдаётся.</p><h2>Четыре места, где компилятор работает вхолостую</h2><h3>1. Panic в состоянии Returned</h3><p>Контракт future требует только отсутствия UB. Паниковать при повторном poll — не обязательно. Можно просто вернуть Pending снова: ничего опасного не произойдёт, а ветка panic — это лишний побочный эффект, который плохо оптимизируется.</p><p>Дион сделал экспериментальный патч rustc: в release-режиме Returned больше не паникует. На embedded-прошивках это дало <b>2–5% экономии бинарного размера</b>. В debug-сборках панику можно оставить, чтобы быстро ловить ошибочный повторный poll, — по аналогии с overflow-checks.</p><h3>2. State machine без await</h3><p>Взгляните на функцию без единой await-точки:</p><p>Идеальная реализация — всегда возвращать Poll::Ready(5) безо всякого enum. А rustc генерирует полноценный CoroutineLayout с тремя служебными состояниями и switch по дискриминанту. Такие функции часто появляются в трейтах, где один интерфейс должен быть async, но конкретная реализация ничего не ждёт. Патч, убирающий state machine у async-блоков без await, даёт ещё <b>0,2%</b> размера — мало, но правка тривиальная.</p><h3>3. Futures не инлайнятся через await</h3><p>Классический шаблон: адаптер просто пересылает вызов вниз.</p><p>Сейчас bar получает собственный state machine, который вызывает state machine foo. Вручную мы бы написали fn bar(blah) -&gt; impl Future { foo(blah) } и избавились бы от лишнего enum. Аналогично с преамбулой и постамбулой: их можно перенести на FutureExt::map из крейта futures.</p><h3>4. Одинаковые состояния не схлопываются</h3><p>Если в async-функции несколько веток match ведут к одинаковому await, компилятор создаёт отдельное состояние на каждую ветку:</p><p>Здесь send_response вызывается с разными аргументами, но структура состояний дублируется. Если вынести выбор аргумента за await, получится одно состояние вместо двух:</p><p>В одном примере из статьи MIR сокращается с 456 до 302 строк, а ассемблер — примерно на 11%. Оптимизации stack, поэтому выигрыш в реальном коде может быть заметнее.</p><h2>Что делать сегодня, не дожидаясь rustc</h2><p>Всё перечисленное выше — это работа для команды компилятора. Но релиз с этими патчами может занять месяцы, а то и годы. Пока он не вышел, можно снизить async-bloat в своём коде.</p><p><b>Главный принцип:</b> каждый лишний async fn — это лишний state machine. Если функция не содержит await, не делайте её async.</p><h3>Заменяйте async-пустышки на impl Future</h3><p>Типичный случай — трейт, где одна реализация реально ждёт ввода-вывода, а другая просто возвращает значение.</p><p>Вместо полноценного state machine получается готовая future, которая сразу возвращает Ready. Это особенно полезно в embedded-HAL, где трейты часто async, но конкретный драйвер может делать только прямой доступ к регистрам.</p><h3>Прозрачные обёртки без await</h3><p>Если функция только пересылает await вниз, уберите await:</p><h3>Схлопывайте await-точки</h3><p>Если несколько веток match заканчиваются одним и тем же await, вынесите выбор аргументов наружу. Это уменьшает число состояний state machine и облегчает жизнь LLVM.</p><h3>Передавайте большие данные по ссылке</h3><p>Async-функция захватывает в state machine всё, что живёт через await. Если передать массив по значению, он окажется внутри future целиком:</p><p>Разница в 52 раза по размеру future — и это без учёта memcpy, который компилятор вынужден вставлять при перемещении больших значений.</p><h2>Перспективы: Project Goal и финансирование</h2><p>Дион оформил эти идеи как <a href="https://rust-lang.github.io/rust-project-goals/2026/async-statemachine-optimisation.html" rel="noopener">Rust Project Goal</a> — формальный механизм, через который команды заявляют цели на полугодие. По его оценке, работа требует порядка <b>€30 000</b> финансирования. Для компиляторного проекта это скромная сумма: речь идёт о 2–5% размера прошивки практически в любом async-проекте.</p><p>Есть и другой подход к той же проблеме: не убирать state machine на входе, а научить LLVM лучше их оптимизировать на выходе. Дион считает, что оба направления дополняют друг друга. Чем проще state machine попадает в LLVM, тем эффективнее её можно проинлайнить и упростить на поздних проходах.</p><h2>Выводы</h2><blockquote>Async Rust never left the MVP state. The compiler generates state machines with a lot of unnecessary baggage. With a few targeted optimizations in rustc we can get smaller binaries and better performance for everyone.</blockquote><p>Утверждение «async Rust так и не вышел из MVP» звучит резко, но поясняет, почему наши «zero-cost» абстракции иногда всё-таки стоят дорого. Компилятор делает корректный, но не оптимальный код: лишние panic-ветки, неинлайненные futures, дублирующиеся состояния и state machine там, где они не нужны.</p><p>Для embedded и WASM это не абстрактная проблема, а конкретные килобайты прошивки. Пока rustc учится, разработчик может снизить налог вручную: убирать async у функций без await, возвращать impl Future из прозрачных обёрток, схлопывать await-точки и не передавать большие структуры по значению.</p><p>Если ваш проект на Rust бьётся о лимит flash, имеет смысл посмотреть на async-граф вызовов свежим взглядом. Часто проще убрать одну лишнюю async-обёртку, чем месяцами ждать патч в компиляторе.</p><p><b>Источники:</b><br />— <a href="https://tweedegolf.nl/en/blog/237/async-rust-never-left-the-mvp-state" rel="noopener">Dion Dokter, «Async Rust never left the MVP state»</a>;<br />— <a href="https://tweedegolf.nl/en/blog/235/debloat-your-async-rust" rel="noopener">Dion Dokter, «Debloat your async Rust»</a>;<br />— <a href="https://rust-lang.github.io/rust-project-goals/2026/async-statemachine-optimisation.html" rel="noopener">Rust Project Goal: Async state machine optimisation</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Куда двигаться после изучения Django: советы для Python-разработчиков</title>
      <link>https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299</link>
      <comments>https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Евгения Епихина]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299</guid>
      <description><![CDATA[<p>В статье разбираемся, почему Django — далеко не финиш в карьере, и в каких направлениях можно двигаться Python-разработчику.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/kuda-dvigatsya-posle-izucheniya-django--sovety-dlya-python-razrabotchikov-257299">Куда двигаться после изучения Django: советы для Python-разработчиков</a>»</p>]]></description>
      <category><![CDATA[API]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Парсинг]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[MySQL]]></category>
      <category><![CDATA[Машинное обучение]]></category>
      <category><![CDATA[SQL]]></category>
      <category><![CDATA[Raspberry Pi]]></category>
      <category><![CDATA[DevOps]]></category>
      <category><![CDATA[Работа]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[Базы данных]]></category>
      <category><![CDATA[Навыки]]></category>
      <category><![CDATA[Django]]></category>
      <category><![CDATA[Docker]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Data Science]]></category>
      <category><![CDATA[NoSQL]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[аналитик]]></category>
      <category><![CDATA[PostgreSQL]]></category>
      <category><![CDATA[Neo4j]]></category>
      <category><![CDATA[Бизнес]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 12 Aug 2025 14:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Django — это веб-фреймворк на языке Python, который позволяет быстро создавать сложные веб-приложения. Он включает в себя готовые компоненты для работы с базами данных, маршрутизацией URL, обработкой форм, аутентификацией пользователей и админ-панелями, что значительно ускоряет разработку и упрощает поддержку проектов.</p><p>Владение Django — это старт, а не финиш. Чтобы оставаться востребованным, нужно постоянно расширять знания и навыки. В этой статье разберем пути и направления для улучшения своих компетенций.</p><h2>Почему владение Django — не предел для разработчика</h2><h2>Особенности Django</h2><p>Django используют для разработки веб-приложений разной сложности: при работе с большими базами данных, для создания сервисов, способных обслуживать большое количество пользователей. На нём создают соцсети, новостные сайты, веб-версии приложений, онлайн-магазины.</p><p>Основные плюсы:</p><ul><li><b>Полноценный стек</b>: ORM для работы с базой, мощная система маршрутизации URL, шаблоны для рендеринга, встроенная админка, формы, система аутентификации и авторизации.</li><li><b>Архитектура MTV (Model-Template-View)</b>: похожа на классический MVC, но с особенностями, которые упрощают разделение логики, представления и данных.</li><li><b>Безопасность</b>: Django автоматически защищает от CSRF, XSS, SQL-инъекций и других распространенных атак. Не нужно писать много дополнительного кода.</li><li><b>Активное сообщество и экосистема</b>: тысячи сторонних пакетов, расширений и готовых решений.</li><li><b>Поддержка нескольких баз данны</b>х: PostgreSQL, MySQL, SQLite, Oracle и др.</li></ul><p>Ограничения:</p><ul><li><b>Синхронная природа Django</b>.</li><li><b>Монолитность</b>: архитектура фреймворка ориентирована на создание крупных приложений, но в микросервисах может быть избыточна.</li><li><b>Ограниченная гибкость ORM</b>: нестандартные SQL-запросы иногда сложно выразить средствами ORM, приходится использовать raw SQL или сторонние библиотеки для запросов.</li><li><b>Строгие правила организации кода</b>: требуют дисциплины и могут ограничивать свободу в архитектурных решениях.</li><li><b>Недостаточная производительность</b>: уступает лёгким асинхронным фреймворкам (например, FastAPI), особенно под высокими нагрузками. Но для большинства проектов пока это не критично.</li></ul><h2>В каком направлении двигаться после изучения Django</h2><blockquote>Задача — создавать продукт, который будет нужен конечному потребителю.</blockquote><h3>Первое направление для развития — расширить инструментарий для решения разных задач в веб-разработке</h3><p>Возможные пути:</p><ul><li>Изучить другие веб-фреймворки (Flask, FastAPI)</li><li>Углубиться в асинхронное программирование (asyncio, aiohttp)</li><li>Работать с API и микросервисами</li></ul><h4>Flask и FastAPI</h4><p>Flask — минималистичный микрофреймворк, даёт полную свободу в выборе компонентов. Используют для небольших приложений и микросервисов.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-08-12/bbe7c040-14de-426d-9a10-c556f7319bee.png" alt="" /><figcaption>Пример простой команды на Flask</figcaption></figure><p>FastAPI — современный асинхронный фреймворк, ориентирован на создание высокопроизводительных API. Поддерживает стандарт OpenAPI и автоматическую генерацию документации. Он быстрее Flask и Django благодаря asyncio и Pydantic.</p><figure><img src="https://media.tproger.ru/user-uploads/102593/2025-08-12/33a27277-5136-4a03-8af2-f436db42fa99.png" alt="" /><figcaption>Пример простого API на FastAPI</figcaption></figure><h4>Асинхронное программирование</h4><p>Веб-разработка всё активнее использует асинхронные технологии. Django не всегда справляется с задачами высокой конкурентной нагрузки.</p><p>Поэтому изучение asyncio — стандартной библиотеки Python для асинхронного программирования — откроет перед вами новые возможности. Вместе с aiohttp или тем же FastAPI вы сможете создавать приложения, которые обрабатывают тысячи одновременных соединений. Это особенно важно для real-time сервисов, чат-приложений и систем с интенсивным обменом данными.</p><h3>Второе направление — расширить навыки в смежных областях</h3><p>Можно пойти по пути расширения компетенций за пределы основной специализации. Важно не только уметь писать код, но и понимать, как приложения разворачиваются и работают в продакшене. Знание DevOps-практик помогает наладить эффективное взаимодействие между разработкой и эксплуатацией.</p><h4>Изучение DevOps и контейнеризации</h4><p>Контейнеры позволяют запускать приложения в изолированной среде, это упрощает настройку и развертывание. Например, Docker помогает упаковать приложение с зависимостями в один контейнер, а Kubernetes — управлять такими контейнерами в продакшене. Знание этих технологий улучшит взаимодействие с операционной командой и ускорит выпуск новых версий приложений.</p><h4>CI/CD и автоматизация процессов</h4><p>Непрерывная интеграция (Continuous Integration) и непрерывное развертывание (Continuous Deployment) — ключевые практики современной разработки ПО. Они позволяют автоматизировать сборку, тестирование и доставку приложений, масштабировать процессы.</p><p>Инструменты CI/CD (например, Jenkins, GitLab CI/CD, GitHub Actions) помогают настроить автоматические пайплайны, которые обеспечивают быструю обратную связь и минимизируют человеческий фактор в релизах. Автоматизация процессов снижает количество ошибок и позволяет сосредоточиться на разработке новых функций.</p><p>Настройка непрерывной интеграции и доставки (Continuous Integration / Continuous Delivery) снижает поток ошибок при релизах и экономит время. Пример: GitHub Actions для автоматического запуска тестов и сборки проекта при каждом коммите.</p><h4>Больше знаний в области баз данных</h4><p>Помимо классических реляционных баз данных (PostgreSQL, MySQL), современные приложения часто используют NoSQL для специфичных задач. MongoDB, Redis, Cassandra обеспечивают гибкость в хранении данных, горизонтальное масштабирование и высокую производительность при работе с большими объемами информации.</p><p>Графовые базы данных (Neo4j, ArangoDB) предназначены для эффективного хранения и анализа связей между объектами, что важно для социальных сетей, рекомендательных систем и других приложений с богатой структурой.</p><p>Так, Redis хорошо подходит для кэширования данных, а Neo4j — для сложных связей между объектами.</p><h3>Третье направление — переход к другим аспектам Python-разработки</h3><p>Рассмотрим четыре варианта карьерного развития для Python-программиста: Data Science и машинное обучение, автоматизация бизнес-процессов, разработка десктопных приложений и встраиваемые системы (IoT).</p><p>Почему стоит попробовать?</p><ul><li>Высокий спрос на специалистов. Они востребованы в банках и инвестиционных компаниях, в сфере медицины и биотехнологии, в консалтинге,  автомобильной промышленности и т.д..</li><li>Широкий набор библиотек: pandas, NumPy, scikit-learn, TensorFlow, PyTorch.</li><li>Возможность работать с реальными задачами: от бизнеса до науки.</li></ul><h4>Автоматизация и скрипты для бизнеса</h4><p>Python часто используется для автоматизации рутинных задач: парсинга данных, обработки файлов, интеграции систем, генерации отчетов. Создание скриптов для автоматизации бизнес-процессов помогает повысить эффективность работы и снизить количество ошибок.</p><p>Знание таких библиотек, как openpyxl (работа с Excel), requests (HTTP-запросы), BeautifulSoup и Scrapy (парсинг веб-страниц), а также умение писать скрипты под конкретные задачи, делают разработчика ценным специалистом в корпоративной среде.</p><p>Примеры задач:</p><ul><li>Автоматическая загрузка данных из Excel и их преобразование</li><li>Скрипты для отправки email-рассылок</li><li>Интеграция с CRM и другими сервисами через API</li></ul><h4>Разработка десктопных приложений (PyQt, Kivy)</h4><p>Хотя сейчас популярность уходит к вебу и мобильным платформам, десктопные приложения на Python востребованы в таких сферах: инструменты для анализа, редакторы, утилиты.</p><p>Инструменты для создания:</p><ul><li>PyQt — мощный фреймворк для создания кроссплатформенных GUI.</li><li>Kivy — библиотека для разработки приложений с поддержкой сенсорных экранов.</li></ul><p>Этот путь подходит тем, кто хочет создавать удобные инструменты с графическим интерфейсом для пользователей на Windows, macOS или Linux.</p><h4>Встраиваемые системы и IoT</h4><p>В области IoT и встроенных систем Python набирает популярность благодаря легкости освоения и поддержке на маломощных устройствах. Помогают в этом  платформы по типу Raspberry Pi и MicroPython.</p><p>Изучение этого направления открывает возможности работы с аппаратным обеспечением, созданием прототипов и внедрением инновационных решений в промышленности и бытовой технике.</p><p>Например, с помощью Python на Raspberry Pi можно  разрабатывать датчики для мониторинга состояния оборудования на производстве и разрабатывать прототипы носимых устройств для сбора данных о здоровье.</p><blockquote>Если рассматривать профессию “Python-разработчик на Django”, то сразу получится сужение до конкретной библиотеки на конкретном языке. Если же в резюме у специалиста стоит, что он “разработчик Python”, возможностей сильно больше. Если написать про себя “разработчик”, будет не понятно, разработчик чего. Но изменив резюме на “DevOps инженера”, становится понятен карьерный трек.</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>Если бы я хотел стать разработчиком на Rust в 2025, с чего бы я начал?</title>
      <link>https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-</link>
      <comments>https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-</guid>
      <description><![CDATA[<p>Гайд по Rust. Показываем, что нужно знать, чтобы научиться языку программирования Раст. Рассматриваем пошаговую инструкцию и практические примеры ✔ Tproger</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/esli-by-ya-hotel-stat-razrabotchikom-na-rust-v-2025--s-chego-by-ya-nachal-">Если бы я хотел стать разработчиком на Rust в 2025, с чего бы я начал?</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[C++]]></category>
      <category><![CDATA[Операционные системы]]></category>
      <category><![CDATA[Git]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[C#]]></category>
      <category><![CDATA[Советы]]></category>
      <category><![CDATA[GitHub]]></category>
      <category><![CDATA[Haskell]]></category>
      <category><![CDATA[Rust]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[WebAssembly]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Пет-проект]]></category>
      <category><![CDATA[Разработка]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 03 Apr 2025 10:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Rust — язык общего назначения, который ориентирован на высокую производительность и безопасное управление памятью. На нем можно писать софт практически для любого направления: CLI-утилиты, высоконагруженные сервера, десктопные приложения, мобильные приложения (с некоторыми оговорками), игры и игровые движки, прошивки для микроконтроллеров, операционные системы, драйвера и даже браузерные приложения (через компиляцию в WebAssembly).</p><p>В рейтинге языков программирования TIOBE Rust занимает 14 место. Для сравнения — в прошлом марте он был на 17 позиции. Вместе с экспертами <a href="https://solvery.io/?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=main_page">Solvery</a> <a href="https://solvery.io/ru/mentor/bondiano?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=vasiliy_kuzenkov">Василием Кузенковым</a>, full-stack разработчиком в Web3 стартапе, и <a href="https://solvery.io/ru/mentor/belyaev_dmitry?utm_source=article&amp;utm_medium=partner&amp;utm_term=tproger&amp;utm_content=rust_2025&amp;utm_campaign=belyaev_dmitriy">Дмитрием Беляевым</a>, Rust developer в Wildberries, разбираемся, как стать разрабом на Расте в 2025 году.</p><h2>Немного об особенностях</h2><p>Если вы до этого программировали на ООП языках, то вам, возможно, бросалось в глаза отсутствие привычных классов. Вместо них здесь алгебраические типы данных и трейты для решения expression problem. Их механизм куда больше похож на typeclasses из Haskell, что также будет для вас новой концепцией при построении крупных приложений, и нужно будет перестраивать мышление.</p><blockquote>Также когда стартуешь, немного непривычно работать с move-семантикой, RAII, borrow checker’ом и лайф-таймами. Но компилятор сыпет довольно подробными ошибками, которые можно легко поправить, если разобраться.</blockquote><p>А еще у Rust очень строгая типизация и очень мощная система типов, а сами типы построены так, чтобы предоставлять некоторые гарантии программисту. Например, ссылки в Rust всегда ссылаются на объект, гарантировано существующий в памяти, а стандартные строки содержат только валидный UTF-8. При этом в подавляющем большинстве случаев тип необязательно указывать явно, компилятор способен выводить типы, анализируя контекст функции целиком. Кроме того, Rust следует идеологии «компилируется, значит, работает». От логических ошибок, конечно, Раст не спасёт, но тем не менее очень большой пласт багов можно отловить на этапе кодинга. Да, это сложно, но лучше помучиться при разработке, чем потом разбираться почему упал прод.</p><p>Ещё одна отличительная фишка — абстракции с нулевой стоимостью. Rust позволяет писать высокоуровневый и понятный код, который при этом будет иметь ту же производительность, что и более низкоуровневый оптимизированный вручную вариант.</p><blockquote>Хороший пример здесь — итерация по различным коллекциям. Многие языки позволяют использовать итераторы с их абстракциями вроде map или filter. Только такой код, как правило, будет в несколько раз медленнее, чем если то же самое переписать на циклы. Компилятор Rust способен развернуть такой итератор в обычные циклы сам, и производительность будет сравнима, а порой даже лучше, так как программисты часто при написании низкоуровневого кода заставляют процессор делать много лишних вычислений.</blockquote><h2>Сложно ли переходить на Rust</h2><p>У Rust достаточно нетривиальная кривая входа, и без понимания некоторых важных принципов сложно написать код, который хотя бы будет компилироваться. Это относится не только к полным новичкам в программировании, но и к людям, которые уже владеют другим языком.</p><blockquote>На рынке фактически все вакансии требуют уже какого-то опыта в разработке. И сложность перехода сильно разнится от вашего текущего стека. Для Go-программиста переход будет средне-сложным, а язык, возможно, покажется перегруженным. Для С++ — менее сложным, но язык покажется местами ограничивающим. Я переходил на него с JS/TS’а и столкнулся со множеством низкоуровневых концепций, о которых раньше мог не задумываться. Но если у вас есть опыт в системном программировании — переходить будет в разы проще.</blockquote><p>Однако Rust прививает программисту очень много хороших привычек, которые меняют подход к написанию кода и на других языках. Это однозначно хороший выбор в качестве первого языка, но при условии, что у вас есть достаточно времени и терпения на освоение.</p><blockquote>В моей практике менторства много успешных кейсов перехода на Rust с самых разных языков, но проще всего он даётся тем, кто раньше писал на современном C++ и уже понимает такие концепции, как move-семантика и RAII. Много привычного здесь обнаружат и те, кто писал на функциональных языках (Haskell  или OCaml). Но в целом, для остальных тоже нет никаких проблем, даже если вы совсем новичок.</blockquote><h2>С чего начать изучать Rust</h2><p>Вместе со стартом в изучении языка с The Rust Book — бесплатной официальной книгой по Rust’у — стоит углубить свои знания в более низкоуровневых вещах: в чем разница стека и кучи, что такое разметка памяти и адресация в памяти, как работает процессор, подходы и проблемы многопоточного программирования, плюс почитать про операционные системы и сети. Если вы решили осознанно применять Rust, оно вам пригодится.</p><blockquote>Начать можно даже имея только самую базу в программировании: переменные, ветвления, циклы, функции. Крайне желательно разобраться в устройстве памяти, что такое стек и куча, а так же какие области памяти бывают помимо них.</blockquote><p>Вот примерный список того, что нужно учить на старте:</p><ul><li><b>Переменные.</b> Они по умолчанию неизменяемые (let x). Чтобы x стал изменяемым, нужно указать это явно через let mut. Константы (const) и статические переменные (static) вам будут нужны редко, они имеют свои особенности.</li><li><b>Типы.</b> Стоит разобраться с составными типами, такими как массивы и кортежи, а также с пользовательскими объявляемыми конструкциями struct (тип-произведение) и enum (тип-сумма). Также типы данных в Расте есть стандартные: bool, i32, u64 и прочие числовые, но строки могут удивить, так как их видов сильно больше.</li><li><b>Match.</b> Нужно понять такую вещь, как pattern-matching, познакомиться с оператором match, а также осознать, что pattern-matching применяется не только в нём, а везде, где возможно объявление переменных.</li></ul><p>Таким образом, вы можете сразу проверить что-то и присвоить результат в переменную:</p><p>Здесь мы сразу проверили и присвоили:</p><p>Тут нам потребовалась дополнительная изменяемая переменная.</p><h2>Что такое Cargo</h2><p>Cargo —  это консольная утилита, которая устанавливается вместе с компилятором языка. Она служит одновременно для управления зависимостями, сборки проекта и запуска тестов. Плюс для Cargo есть расширения, например, в поставке по умолчанию уже есть форматтер и линтер clippy.</p><p>Cargo рассчитан на то, что вы будете запускать его через терминал, самые полезные команды это:</p><ul><li>cargo new — создаёт новый шаблонный проект в указанной папке;</li><li>cargo build — собирает проект;</li><li>cargo run — собирает проект и запускает получившийся исполняемый файл;</li><li>cargo check — dry-run сборки, делает все проверки компилятора, но ничего не собирает, что заметно быстрее полноценной сборки;</li><li>cargo test — собирает проект со всеми тестами и запускает их;</li><li>cargo fmt — форматирует проект в общепринятый стиль кода;</li><li>cargo clippy — запускает линтер, очень полезно, можно подсказать более оптимальные варианты кода, найти некоторые потенциальные логические ошибки;</li><li>cargo install — устанавливает пакет, содержащий исполняемые файлы;</li><li>cargo clean — очищает все артефакты сборки.</li></ul><p>Cargo позволяет описать структуру, настройки (профили сборки, описание крейта) и зависимости вашего крейта или даже монорепозитория (с помощью workspace). Кроме непосредственного запуска через терминал, многие вещи могут запускаться через средства интеграции в IDE, такие как rust-analyzer для VSCode.</p><h2>Как работать с ownership, borrowing и lifetimes?</h2><p>Для многих новичков системы владения (ownership), заимствования (borrowing) и времен жизни (lifetimes) выливаются в борьбу с компилятором раста. Эти механизмы нужны в первую очередь для безопасности памяти без сборщика мусора. Общее правило владения такое:</p><p><i>Каждое значение в Rust имеет переменную, которая называется его <b>владельцем</b>. В каждый момент времени может быть только <b>один владелец</b>. Когда владелец выходит из области видимости, значение уничтожается.</i></p><p>Когда значение перемещается (передается другой переменной или функции), владение переходит, и исходная переменная становится недействительной.</p><p>Для типов, реализующих трейт Copy (например, целые числа, булевы значения), значения копируются автоматически:</p><p>Для более сложных типов нужно использовать метод clone(), чтобы создать глубокую копию. В местах программы, где производительность не так важна — использование clone() не возбраняется.</p><p>Если же у вас критичный к производительности кусок кода, то вам также понадобится заимствоватние и лайфтаймы.</p><p>Заимствование позволяет использовать значение без получения владения через ссылки (&amp; и &amp;mut). Для мутабельных ссылок `&amp;mut` есть дополнительные ограничения: в каждый момент времени может существовать только одна изменяемая ссылка на значение.</p><p>Нельзя иметь изменяемую ссылку, если уже есть неизменяемая ссылка на то же значение.</p><p>Времена жизни же гарантируют, что ссылки действительны на протяжении всего времени их использования.</p><p>Аннотация 'a указывает, что возвращаемая ссылка будет жить как минимум столько же, сколько кратчайшая из входных ссылок.</p><p>Общие советы по заимствованию здесь такие:</p><ul><li>Используйте ссылки, когда не нужно владение.</li><li>Возвращайте значения из функций для передачи владения обратно.</li><li>Используйте клонирование для создания новых экземпляров (с пониманием стоимости).</li><li>Используйте типы с трейтом Copy, когда это возможно.</li><li>Еще полезно не забывать о контейнерах. Rc&lt;T&gt; позволяет иметь несколько владельцев одного значения через счетчик ссылок, а RefCell&lt;T&gt; обеспечивает проверку правил заимствования в рантайме.</li></ul><h2>О структурах, перечислениях, модулях и функциях, замыканиях, итераторах</h2><p>Структуры (structs) в Rust позволяют создавать пользовательские типы данных, объединяющие связанные значения и выступающие типом произведения.</p><p>Перечисления (enum) позволяют определить тип, перечисляя все возможные варианты значений.</p><p>Перечисления в Rust являются типами суммы, поскольку значение может быть одним из вариантов:</p><p>Тип Shape представляет объединение (сумму) всех возможных вариантов. И к этому есть мощное сопоставление с образцом для работы с ADT:</p><p>Модули позволяют организовать код и контролировать видимость элементов. Определяются они через синтаксис mod &lt;name&gt; {} и могут быть вложенны друг в друга:</p><p>Также модули могут быть организованы в различных файлах (имя файла в таком случае будет именем модуля):</p><p>Импорт модуля осуществляется через ключевое слово use:</p><p>Функции в Rust определяются через ключевое слово fn:</p><p>Можно делать функции высшего порядка и передавать в них, как обычные, так и анонимные функции:</p><p>Еще одним элементом функционального программирования в Rust выступает итератор:</p><p>Здесь мы создаем собственный итератор Counter, реализуя стандартный трейт Iterator. Он используется для многих встроенных коллекций, таких как Vec или HashMap. Этот трейт особенно удобен из-за различных функциональных комбинаторов из стандартной библиотеки:</p><p>Трейт Iterator предоставляет множество методов адаптеров, таких как enumerate, filter или map, которые возвращают новый итератор. Методы адаптеров ленивые, они не запускают итерацию. Также есть методы исполнители, которые итерируют пока не закончатся значения, например, collect, fold или count.</p><p>Большинство коллекций (и ссылки на них) реализуют трейт IntoIterator (способность кастоваться в Iterator). Также IntoIterator автоматически реализуется для любого Iterator (ничего не стоящий каст сам в себя). Цикл for в Rust работает только с объектами, реализующими IntoIterator.</p><h2>Как обрабатывать ошибки в Rust</h2><p>В Rust принято разделять ошибки на 2 вида: паники и результаты операций:</p><ul><li><b>Паники</b> используются для непредвиденных ситуаций и ошибок программиста, например, деление целочисленного типа на 0 или выход за границу массива. И хотя паники можно отловить, стандартное и рекомендуемое поведение при них — программа упадёт, будет напечатан стектрейс.</li><li><b>Результаты операций</b> выражаются типом Result&lt;T, E&gt;, который является перечислением из двух вариантов — Ok(T) и Err(E). Такой подход гарантирует, что все ошибки строго типизированы, а без обработки ошибки невозможно извлечь результат операции.</li></ul><p>Option&lt;T&gt; используется для представления значения, которое может отсутствовать:</p><p>Result&lt;T, E&gt; используется для операций, которые могут завершиться ошибкой:</p><p>Для удобства проброса ошибок наверх существует оператор ?, который пишется после любого выражения, возвращающего Result, и возвращает Ok вариант. В случае Err варианта будет выход из функции с возвращением ошибки.</p><p>Обычно все ошибки описываются в перечислениях, а для уменьшения шаблонного кода используются крейты вроде <a href="https://google.github.io/comprehensive-rust/error-handling/thiserror.html">thiserror</a>.</p><h2>Про асинхронное программирование</h2><p>Асинхронное программирование в Rust строится вокруг трейта Future— его реализуют для типов, представляющих значение, которое будет доступно в будущем.</p><p>Также в Rust есть синтаксис async/await. Ключевым словом async могут быть отмечены функции и блоки кода — они будут возвращать анонимный тип, реализующий Future. Async-блоки также могут захватывать окружение подобно замыканиям. Внутри async-блоков и функций возможно использовать ключевое слово await на любом выражении, возвращающем Future или IntoFuture (способность кастоваться к Future). В отличие от других языков с подобным синтаксисом, await записывается через точку после выражения, что очень удобно для построения цепочек вычислений.</p><p>Для исполнения асинхронного кода необходим рантайм, но стандартная библиотека такого рантайма не предоставляет, поэтому приходится использовать сторонние библиотеки. Самым популярным рантаймом является библиотека tokio.</p><blockquote>В асинхронное программирование на Rust я рекомендую приходить уже после углубленного изучения языка, первых пет-проектов и небольшой работы с многопоточным кодом. Хотя синтаксис и общие правила работы с асинхронным кодом покажутся знакомыми тем, кто знает JS или C#, из-за более низкоуровневой природы языка работать с ним немного сложнее.</blockquote><h2>Что еще должен знать новичок в Rust</h2><p>Вот примерный список:</p><ul><li>Очень желательно погрузиться в устройство памяти процесса, узнать, что помимо стека и кучи существуют и другие области (например, исполняемый машинный код так же отражён на память, а static-переменные хранятся не в стеке и не в куче, а в своей собственной области). Неплохо было бы и разобраться с тем, что у типов помимо размера есть выравнивание. Что в Rust бывают ZST (zero size type) — типы, размер которых честный 0, и DST (dynamic size type) — типы, размер которых неизвестен во время компиляции.</li><li>Обязательно разобраться, как Rust освобождает память, не используя сборщик мусора. Почитать, что такое RAII. Понять, как работает трейт Drop.</li><li>Избавится от стереотипов о Rust. Rust — не самый сложный язык, как только вы поймёте, как он работает. Плюс платят за Rust, как правило, больше, чем на аналогичных позициях на других языках.</li><li>Оставить свои привычки из других языков (за исключением разве что Haskell/OCaml). Здесь не получится писать, как на C++/Java/Go и т.д. Привыкайте к хорошему и станете лучше, чем были до освоения Rust.</li></ul><h2>Что изучать, если есть вся база: чек-лист</h2><ul><li>Макросы и метапрограммирование</li><li>Unsafe Rust для низкоуровневого контроля</li><li>Интеграция с C/C++ через FFI</li><li>Разработка встраиваемых систем</li><li>WebAssembly</li><li><a href="https://doc.rust-lang.org/nomicon/">rustnomicon</a></li><li>Undefined Behavior</li><li>unsafe-код</li></ul><h2>Тренды на 2025 год</h2><p>На Rust’е пишут все. Более полный список можно посмотреть тут: <a href="https://github.com/rust-unofficial/awesome-rust">https://github.com/rust-unofficial/awesome-rust</a></p><p>Вот несколько топовых фреймворков и библиотек:</p><ul><li>serde — фреймворк для сериализации/десериализации</li><li>tokio, futures — для асинхронного программирования</li><li>clap — парсер аргументов командной строки</li><li>anyhow, thiserror — удобная работа с ошибками</li><li>chrono — работа с датой и временем</li><li>dashmap — многопоточная hashmap</li><li>bytes — эффективная работа с сырыми байтами</li><li>log, tracing — для логирования</li><li>reqwest — для http запросов</li><li>axum — для http сервера и REST api</li><li>mockall, test-case — упростит написание тестов</li><li>bevy — игры</li><li>clippy — линтер</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Pub/Sub — когда нужно масштабировать приложения</title>
      <link>https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya</link>
      <comments>https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Лалетин]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya</guid>
      <description><![CDATA[<p>Узнайте, как работает архитектура Pub/Sub для масштабирования приложений, организации обмена данными между микросервисами и создания надёжных систем. Примеры с Apache Kafka, RabbitMQ и Redis.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/pub-sub---kogda-nuzhno-maswtabirovat-prilozheniya">Pub/Sub — когда нужно масштабировать приложения</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Redis]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 28 Dec 2024 09:00:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Когда нужно создать приложение, которое будет справляться с большим количеством пользователей и данных, разработчики используют подход Pub/Sub (сокращение от англ. Publisher/Subscriber, то есть Издатель/Подписчик). Проще всего сравнить эту модель с рассылкой новостей:</p><ul><li><b>Издатель </b>— это тот, кто отправляет информацию. Например, новостной сайт.</li><li><b>Подписчик</b> — это тот, кто подписался и хочет получать информацию. Например, читатель новостного сайта, — он будет получать уведомления о новых статьях.</li></ul><p>Pub/Sub помогает создавать надёжные и быстрые приложения, даже если пользователей много. О том, как это работает, читайте ниже.</p><h2>Что внутри Pub/Sub</h2><p>Чтобы было проще понять, как устроена архитектура Pub/Sub, приведём простую аналогию. Представьте, что у вас есть канал в Telegram. Автор канала (Publisher) пишет сообщение, а все участники (Subscribers) его получают. Если один из подписчиков покидает канал или кто-то, наоборот, присоединяется, автору ничего не нужно менять — он, как и раньше, публикует посты, уведомления о которых приходят всем, кто подписан на этот канал.</p><p>Большие приложения состоят из множества независимых частей — микросервисов. Чтобы приложение работало правильно, микросервисы должны быстро обмениваться данными. И как раз для этого разработчики и используют подход Pub/Sub.</p><p>Мы уже рассказали об издателях и подписчиках, но в этой архитектуре также участвуют и брокеры (или посредники) — именно они отвечают за доставку сообщений.</p><p>Забегая вперёд скажем, что именно брокер обеспечивает масштабируемость в системе. Он организует передачу сообщений и не блокирует выполнение других операций, даже если участников становится очень много. Код брокера обычно пишут на Python или Java, используя при этом дополнительные библиотеки.</p><p>Главная идея такая: издатель не знает, кто получит его сообщения, а подписчики не запрашивают эти сообщения напрямую. Всё происходит через посредника. Это работает следующим образом:</p><ol><li>Издатель отправляет сообщение (например, текст или видео) в определённый канал — он называется темой (Topic).</li><li>Получатели подписываются на нужную тему, чтобы принимать эти сообщения.</li><li>Как только сообщение отправлено в тему, все подписчики его получают.</li></ol><p>Этот процесс проиллюстрирован на следующей схеме:</p><figure><img src="https://media.tproger.ru/user-uploads/100503/2024-12-19/d0de5fc7-4786-41fa-a1d5-881e7f3a7cfb.png" alt="" /><figcaption>Помимо всех действующих лиц на схеме вы можете увидеть каналы. Input channel — это место, куда издатель отправляет сообщения, а output channel — место, откуда подписчики получают эти сообщения.</figcaption></figure><p>Обмен сообщениями происходит асинхронно. Это значит, что отправитель (или издатель) не ждёт, когда получатель (или подписчик) обработает сообщение. Поэтому компоненты приложения могут работать независимо друг от друга, но при этом обмениваться данными.</p><p>Паттерн Pub/Sub тем и отличается от стандартных алгоритмов, в которых очередь из сообщений продолжает формироваться, пока пользователи либо службы не сделают запрос и не извлекут их.</p><p>Итак, почему же эта архитектура так удобна?</p><p>Суть в том, что разработчикам не нужно вручную прописывать, кто кому и что отправляет. Например, если издатель опубликовал событие «пользователь сделал заказ», система сама оповестит все нужные сервисы: склад получит команду проверить товар, доставка начнёт планировать маршрут, а клиент получит соответствующее уведомление. И всё это будет происходить параллельно.</p><p>Сообщение обязательно дойдёт до всех подписчиков, если они не настроили фильтры, чтобы его игнорировать. Например, если один сервис подписан только на сообщения «заказы», он не будет получать данные из темы «новые пользователи».</p><p>Теперь для наглядности приведём пример простого кода брокера.</p><h2>Пример кода брокера на Python</h2><p>Код можно написать на разных языках, но для примера мы возьмем Python — просто потому, что его легче понять. Мы используем популярную библиотеку для работы с очередями — asyncio. А чтобы продемонстрировать случайную задержку при обработке сообщений, используем библиотеку random.</p><p>В коде реализовано всё то, о чём мы говорили выше: когда издатель публикует сообщение, оно мгновенно передаётся всем подписчикам, которые подписаны на соответствующую тему. Далее каждый подписчик асинхронно обрабатывает сообщение (с задержкой, чтобы имитировать реальную работу приложения).</p><p>Этот код — очень простой пример реализации брокера. Для более сложных систем, например, с постоянным хранением сообщений, обработкой ошибок и масштабированием, нужна инфраструктура серьёзнее. Для её создания используют внешние брокеры сообщений — те же <b>RabbitMQ</b> или <b>Apache Kafka</b>. Об этом мы рассказываем ниже.</p><h2>Как выбрать инструменты для реализации Pub/Sub</h2><p>Как мы уже сказали, брокера для Pub/Sub можно написать на разных языках с использованием дополнительных библиотек. Выбор зависит от масштаба проекта, специфики, количества пользователей.</p><p>Рассмотрим наиболее популярные инструменты.</p><h3>Apache Kafka</h3><p>Это платформа для обмена данными между приложениями в реальном времени. Она позволяет передавать большие объёмы информации быстро и надёжно — можно не переживать, что данные будут потеряны.</p><p>Например, если приложение A хочет отправить данные приложению B, оно не делает это напрямую, а отправляет сообщение в Kafka. Kafka сохраняет эти сообщения и передает их всем заинтересованным приложениям, которые подписались на получение этой информации.</p><p>Kafka особенно полезен там, где нужно работать с большими объёмами данных в реальном времени — например, в системах стриминга видео или аналитики. Она может обрабатывать миллионы событий в секунду и гарантирует, что данные не потеряются.</p><p>Вот где используют Apache Kafka:</p><ul><li><b>LinkedIn</b> использует Kafka для передачи сообщений между микросервисами.</li><li>Netflix применяет Kafka для контроля количества событий, обрабатываемых одновременно, и передачи данных из нескольких потоков.</li><li>The New York Times использует Kafka для публикации новостей в режиме реального времени.</li></ul><p>Приведём пример простой публикации и получения сообщений (учитывайте, что у вас должна быть установлена соответствующая библиотека и запущен Apache Kafka на локальном сервере или в облаке):</p><p>Producer отправляет сообщения в Kafka в определённую тему (в нашем случае — test_topic). Consumer подписывается на эту тему и получает сообщения.</p><p>В реальных системах таких издателей и подписчиков может быть много, и Kafka помогает координировать их работу.</p><h3>RabbitMQ</h3><p>Это брокер сообщений или посредник, который помогает разным приложениям обмениваться данными. Его разработали в 2007 году на Erlang — языке, который отлично подходит для создания устойчивых к сбоям систем.</p><p>RabbitMQ поддерживает несколько протоколов обмена данными, поэтому его можно использовать в разных проектах. Например, он может связывать микросервисы, обрабатывать фоновую информацию и управлять большими объёмами сообщений.</p><p>RabbitMQ работает как почтовая служба:</p><ol><li>Одно приложение отправляет сообщение (письмо).</li><li>RabbitMQ принимает это сообщение и сохраняет его в очереди.</li><li>Другое приложение (подписчик) получает сообщение из этой очереди.</li></ol><p>Особенность RabbitMQ — push-модель. Брокер сам отправляет сообщения получателю сразу, как только они появляются. Получателю не нужно запрашивать данные постоянно — он просто ждёт, пока RabbitMQ пришлёт новые сообщения.</p><p>Эта особенность полезна, когда нужно быстро информировать участников системы о важных событиях. Например, отправлять уведомления о статусе заказа, оповещать системы мониторинга о проблемах или обновлять данные в реальном времени.</p><p>Покажем пример (RabbitMQ можно подключить к Python с помощью библиотеки pika):</p><p>Вот как это работает:</p><p>Сначала Producer отправляет сообщение в очередь test_queue. Затем Consumer подписывается на эту очередь и получает сообщения. RabbitMQ принимает сообщение и доставляет его получателю сразу после появления в очереди (та самая push-модель).</p><h3>Redis</h3><p>Система управления данными, которая поддерживает не только стандартную очередь сообщений, но и паттерн Pub/Sub. Этот инструмент используют для организации обмена сообщениями, при этом он хранит промежуточный контент (например, набранный, но не отправленный текст), управляет базами данных небольших приложений и одностраничных сайтов.</p><p>Redis — идеальный выбор для проектов, где важна скорость доставки информации, в том числе в биржевых и финансовых сервисах. Его применяют также для реализации механизма подписок. Вот пример кода:</p><p>В примере издатель отправляет 5 сообщений с небольшой паузой, а подписчик сразу их получает.</p><h2>Примеры реализации Pub/Sub</h2><p>Паттерн используют в сферах, где нужно организовать быстрый обмен информацией между распределёнными компонентами системы. Автоматизация процессов — ключевое направление реализации Pub/Sub.</p><p>Рассмотрим наиболее актуальные области применения шаблона.</p><h3>Мониторинг системы и мгновенная отправка уведомлений</h3><p>Те самые темы, о которых мы рассказали раньше, создаются для различных категорий данных — например, загрузка процессора, состояние серверов, журналы ошибок. Каждая служба может публиковать свои метрики в топиках, а подписчики (системы визуализации, алерты и дашборды) получают эти данные для обработки.</p><p>Вот пример сценария:</p><ol><li>В крупномасштабной системе мониторинга серверов сообщения о сбоях отправляются в специальный канал.</li><li>Система визуализации Grafana или Prometheus подписана на этот канал и сразу обновляет дашборды.</li><li>В случае критического сбоя на основной системе резервный сервер автоматически включается через подписку на ту же тему.</li></ol><p>Pub/Sub позволяет внедрить автоматические реакции на определенные события. Например, если загруженность сервера превышает 90%, система может отправить сообщение об аварийном переключении нагрузки или даже автоматически включить дополнительные вычислительные узлы.</p><h3>IoT (Интернет вещей)</h3><p>Смарт-устройств становится все больше, и для каждого из них необходимо организовать надёжный метод сбора и передачи информации. Здесь также на помощь приходит Pub/Sub.</p><p>Гаджеты могут выступать издателями: они отправляют показания сенсоров, данные о температуре, движении, состоянии и других параметрах на центральный сервер или в облако.</p><p>Для лучшего понимания приведём пример:</p><ol><li>Датчики движения отправляют сообщения в тему «Безопасность».</li><li>Подписчик — система управления домом — принимает эти сообщения и отправляет уведомления на мобильное приложение пользователя.</li><li>Тем временем умная лампа подписана на другой канал и автоматически включается по сигналу о движении.</li></ol><p>Pub/Sub обеспечивает масштабируемость IoT-систем — новые устройства можно легко подключать к существующим темам, не нарушая работу всей архитектуры.</p><h3>Резервное копирование</h3><p>Многим компаниям важно не только хранить информацию, но и организовать надёжное резервное копирование, чтобы уменьшить риски потери данных. Pub/Sub помогает и здесь: можно автоматически собирать и передавать резервные копии данных в облачные хранилища или на резервные серверы. Вот как это может работать:</p><ol><li>Каждую ночь системы баз данных отправляют уведомление в тему «Резервное копирование».</li><li>Подписчик (облачный сервис хранения) получает это сообщение и инициирует процесс копирования данных.</li><li>Если основной сервер недоступен, сообщение передаётся на резервный сервер, который берёт на себя задачу сохранения.</li></ol><p>Также Pub/Sub позволяет настроить многоуровневое резервирование: копии данных могут одновременно отправляться на несколько хранилищ, что и позволяет минимизировать риски потери информации.</p><h2>Преимущества и недостатки использования Pub/Sub</h2><p>Давайте кратко пройдёмся по основным плюсам этого паттерна:</p><ul><li>Систему легко адаптировать под любое количество пользователей. Новых издателей и подписчиков можно добавлять без потери производительности, а архитектуру тем и посредников менять без затрагивания базовых компонентов.</li><li>Издатели и подписчики работают независимо друг от друга. Это позволяет создавать безопасные, модульные системы, где компоненты не зависят от прямых связей и могут развиваться отдельно.</li><li>Pub/Sub можно использовать с разными языками программирования (например, Python или Java) и интегрировать с различными брокерами сообщений.</li><li>Сообщения доставляются мгновенно, что делает Pub/Sub идеальным решением для сервисов реального времени.</li><li>Сообщения дублируются в хранилищах для гарантированной доставки. Дополнительно обеспечивается проверка подлинности издателей и шифрование данных для защиты информации.</li></ul><p>У паттерна есть и недостатки. Он слишком сложен для использования в небольших приложениях, требует грамотной настройки и сопровождения. Если продукт не нуждается в масштабировании, то применение Pub/Sub — неоправданная трата ресурсов. Не всем системам требуется такой уровень сложности и надёжности.</p><p>Для потоковой передачи медиафайлов Pub/Sub — не лучший выбор, поскольку работает в асинхронном режиме. Для конференций в формате видео, голосовых коммуникаций по протоколу IP технология не подойдёт.</p><h2>Итоги</h2><p>Pub/Sub — это эффективный и сравнительно простой способ организовать обмен данными между компонентами системы. Он лежит в основе работы распределённых приложений с микросервисной архитектурой и обеспечивает передачу информации в реальном времени.</p><p>Технология работает асинхронно, что позволяет системе легко масштабироваться и разделять её на независимые модули. Благодаря брокерам сообщений, которые обрабатывают и распределяют данные, приложения не перегружаются, а обмен информацией становится быстрым и безопасным.</p>]]></content:encoded>
    </item>
    <item>
      <title>JavaScript: Всё ли ты знаешь о работе с асинхронностью?</title>
      <link>https://tproger.ru/articles/javascript--vsyo-li-ty-znaew-o-rabote-s-asinhronnostyu-</link>
      <comments>https://tproger.ru/articles/javascript--vsyo-li-ty-znaew-o-rabote-s-asinhronnostyu-?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Анна Ельцова]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/javascript--vsyo-li-ty-znaew-o-rabote-s-asinhronnostyu-</guid>
      <description><![CDATA[<p>Думаете, что знаете вообще все об асинхронности в JavaScript? Уверены, что так оно и есть. Но проверить свои знания никогда не помешает — для этого как раз собрали эту небольшую викторину.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/javascript--vsyo-li-ty-znaew-o-rabote-s-asinhronnostyu-">JavaScript: Всё ли ты знаешь о работе с асинхронностью?</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Sat, 09 Nov 2024 08:00:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Чтобы успешно сдать тест, рекомендуем прочитать нашу статью → <a href="https://tproger.ru/articles/tutorial-po-javascript-async-x2f-await-izuchaem-callbacks-promises-i-async-x2f-await">Асинхронный JavaScript: изучаем Async/Await, Callbacks и Promises</a>.</p><p><br /></p>]]></content:encoded>
    </item>
    <item>
      <title>Главная проблема новичков в асинхронном программировании на Python</title>
      <link>https://tproger.ru/articles/glavnaya-problema-novichkov-v-asinhronnom-programmirovnaii-na-python</link>
      <comments>https://tproger.ru/articles/glavnaya-problema-novichkov-v-asinhronnom-programmirovnaii-na-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Андрей Баранов]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/glavnaya-problema-novichkov-v-asinhronnom-programmirovnaii-na-python</guid>
      <description><![CDATA[<p>Объясняем, в чем проблема асинхронного программирования на Python, и как она касается синтаксиса высокоуровневой концепции async и await.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/glavnaya-problema-novichkov-v-asinhronnom-programmirovnaii-na-python">Главная проблема новичков в асинхронном программировании на Python</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 18 Sep 2023 10:22:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Асинхронность в Python должен знать почти каждый разработчик на Python, который хочет быть вовлечён в коммерческую разработку. Как минимум это касается веб-программирования, где очень важна скорость отклика приложения (сервиса). Вообще, асинхронность в Python – это отдельный мир, сильно отличающийся от синхронного Python. Наличие одного событийного цикла уже вносит много изменений в привычный порядок вещей. Существует много интересных и одновременно спорных вещей, суть которых скрыт в самой концепции асинхронности.</p><p>Дисклеймер: статья ориентирована на тех, кто хорошо знаком с Python и знает основы асинхронности. Здесь не будут разбираться до мельчайших деталей понятия, используемые в статье для различных пояснений и т.п.</p><h2>Немного о себе</h2><p>На данный момент я работаю junior-разработчиком в отечественной IT-компании над сервисом, полностью построенном на aiohttp. Это моя первая работа, да и работаю я сравнительно недолго, так что я не претендую на звание гуру асинхронного программирования. Однако определённый опыт у меня имеется, и хочется поделиться им с читателем.</p><h2>Источник информации</h2><p>Тут я порекомендую YouTube-канал Олега Молчанова, на котором есть отличный цикл видео про асинхронность в Python. Именно после его просмотра ко мне пришло базовое понимание данной тематики. Также у него есть небольшой курс на Boosty, в котором, кстати, рассматривается проблема, о которой тут пойдёт речь. Однако ввиду того, что этот контент является платным, я принял решение рассказать о ней тут. Эта проблема будет рассматриваться под призмой моего опыта, так что, если у кого-то есть чем поделиться, жду обратной связи в комментариях.</p><h2>Непосредственно к проблеме</h2><p>Итак, наступило время понять, о чём же могут думать не так новички об асинхронности в великом и могучем… Python. А проблема состоит в высокоуровневой концепции async и await. И, таким образом, она разделяется на 2 подпроблемы, качающиеся каждой синтаксической конструкции соответственно.</p><h3>Что не так с пониманием async</h3><p>Тут всё просто. Многие думают, что, написав <b>async def</b>, код в функции автоматически становится асинхронным. Это не так. Если этот код спроектирован как синхронный, он таковым и останется. Так, у нас есть две такие функции:</p><p>Если запустить его через интерпретатор Python версии от 3.5, мы убедимся, что код выполнился линейно (время выполнения, почти равное 6 секундам, говорит об этом).</p><p>Важно помнить, что аббревиатура “asyncio” означает “асинхронный ввод-вывод”. Но даже если эти операции есть в коде, как в нашем случае (time.sleep симулирует операцию ввода), недостаточно написать async def. Помимо этого, необходимо использовать асинхронный аналог функции. Например, для time.sleep это asyncio.sleep.</p><p>Если бы мы использовали асинхронный аналог паузы в программе, время выполнения бы было равно величине самой большой паузы, а именно: 5 секундам. Попробуйте поменять time.sleep на await asyncio.sleep, и убедитесь в этом сами.</p><p>Тогда зачем нужен async, если его добавление, на первый взгляд, ничего не делает? На самом деле, делает. Под капотом у такой функции происходит процесс, инициализирующий из неё корутину, которую впоследствии можно будет поместить в специальный класс Task, который, в свою очередь, будет помещён цикл событий.</p><p>Корутина – это подвид генераторов, который может как принимать данные от других корутин, так и отдавать их. Обычные генераторы могут только генерировать данные, что и следует из их названия.</p><h4>Краткий вывод по async</h4><p>Если вы не понимаете тонкостей работы корутин, не задумывайтесь о них и просто ставьте это ключевое слово к той функции, где планируете использовать асинхронный код. Это обязательное условие в мире асинхронного программирования на Python.</p><p>Однако прописывать его везде, где только можно, не очень хорошая идея: в эффективности код не прибавит (даже наоборот, из-за накладных действий для инициализации корутины).</p><h2>Что не так с пониманием await</h2><p>Эта проблема, по моему мнению, больше вводит в заблуждение, чем первая, и сейчас я постараюсь объяснить, почему. Но сперва скажу, в чём она заключается: многим новичкам может показаться, что await переключает контроль выполнения задач (корутин) в событийный цикл. На самом деле, await просто делегирует определённые действия в другую корутину, а переключением контроля выполнения занимается yield, приостанавливающий выполнение корутины и отдающий какое-то значение.</p><p>Не надо бояться слова “делегировать”. Оно означает передача ответственности за какие-либо действия кому-то другому. В программировании это означает, что мы можем поместить определённую часть кода в отдельную функцию, и вызывать её из другой функции, таким образом, делегируя определённые действия в неё. Такой же принцип и с корутинами и генераторами.</p><p>Давайте убедимся в этом на практике:</p><p>Если бы await отдавал контроль выполнения событийному циклу, программа напечатала бы “hello ! world”, но тут код выполнился последовательно (по порядку, указанному в gather). Всё потому, что await просто вызывает корутину, “проваливается” внутрь её. Стоит отметить, что выполнение корутины, из которой производится await, приостанавливает своё выполнение, ожидая (“wait” с английского), когда выполнится “дочерняя” кореутина. А вот в корутине уже могут быть yield, которые дают шанс выполниться другим задачам в событийном цикле.</p><p>Опять же, поменяв time.sleep на асинхронный аналог, всё встанет на свои места. И тут важно понять: в своей реализации asyncio.sleep использует yield, и именно он является главным фактором в той магии, которая сокращает время выполнения программы и конкурентного выполнения корутин.</p><h2>Краткий вывод по await</h2><p>Всё просто. Проводя аналогии с синхронным Python, если корутина – это функция, то await – это вызов функции (эквивалентно скобкам после названия функции). Всё, ничего более. За конкурентность отвечают другие языковые конструкции.</p><h2>Время выводов</h2><p>В некоторых материалах и видео на YouTube я наблюдал следующие тезисы: “Если ты не знаешь, как писать асинхронный код, просто ставь везде async и await”. И я скажу так: если нужно написать единичный скрипт на Python и это вообще не является твоей профильной сферой, то можно не погружаться в подробности и делать согласно этому тезису. Однако в остальных случаях на такой идеологии далеко не уедешь. Важно понимать, как работает асинхронность под капотом хотя бы на концептуальном уровне, чтобы уметь проектировать асинхронные программы. А async/await – это просто высокоуровневые обёртки, которыми легко пользоваться тем, кто понимает, как они работают. Я постарался раскрыть главную проблему новичков в этом направлении, поверхностно разобрав каждую конструкцию. Надеюсь, что эта статья помогла разобраться в этой концепции и что я направил вас в нужное русло в плане развития навыков в асинхронном программировании на Python. Всем удачи и спасибо за то, что прочитали до конца!</p>]]></content:encoded>
    </item>
    <item>
      <title>Асинхронный JavaScript: изучаем Async/Await, Callbacks и Promises</title>
      <link>https://tproger.ru/articles/tutorial-po-javascript-async-x2f-await-izuchaem-callbacks-promises-i-async-x2f-await</link>
      <comments>https://tproger.ru/articles/tutorial-po-javascript-async-x2f-await-izuchaem-callbacks-promises-i-async-x2f-await?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Рафаил Агазода]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/tutorial-po-javascript-async-x2f-await-izuchaem-callbacks-promises-i-async-x2f-await</guid>
      <description><![CDATA[<p>Рассказываем, как построить приложение с Async/Await на JavaScript. Также объясняем, как работают Callbacks и Promises.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/tutorial-po-javascript-async-x2f-await-izuchaem-callbacks-promises-i-async-x2f-await">Асинхронный JavaScript: изучаем Async/Await, Callbacks и Promises</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 02 May 2023 09:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<p>Сегодня мы попытаемся построить и запустить магазин мороженого, при этом одновременно изучить асинхронный JavaScript. Также вы узнаете, как использовать:</p><ol><li>Callbacks.</li><li>Promises.</li><li>Async / Await.</li></ol><p>Вот что мы рассмотрим в этой статье:</p><ol><li>Что такое асинхронный JavaScript.</li><li>Синхронный и асинхронный JavaScript.</li><li>Как работают Callbacks в JavaScript.</li><li>Как работают Promises в JavaScript.</li><li>Как работает Async / Await в JavaScript.</li></ol><p>Это — перевод <a href="https://www.freecodecamp.org/news/javascript-async-await-tutorial-learn-callbacks-promises-async-await-by-making-icecream/">оригинальной статьи на freeCodeCamp</a> от автора Joy Shaheb.</p><p>Ещё у нас есть видеоверсия урока от автора (осторожно, английский язык):</p><p>Итак, давайте начнём!</p><h2>Что такое асинхронный JavaScript</h2><p>Если вы хотите разрабатывать эффективные проекты, то эта концепция идеально подходит вам.</p><p>Теория асинхронного JavaScript помогает разбивать большие сложные проекты на более мелкие задачи.</p><p>Вы можете использовать любую из этих трех техник – Callbacks, Promises или Async/await – для выполнения небольших задач таким образом, чтобы получить наилучшие результаты.</p><h2>Синхронный и асинхронный JavaScript</h2><h2>Что такое синхронная система</h2><p>В синхронной системе задачи выполняются одна за другой.</p><p>Представьте, что у вас всего одна рука для выполнения 10 задач. Из-за этого вы должны выполнять по одной задаче за один раз.</p><p>Посмотрите на GIF, здесь происходит одно действие за раз:</p><p>Вы увидите, что пока первое изображение не загрузится полностью, второе изображение не начнет загружаться.</p><p>По умолчанию JavaScript является синхронным или однопоточным. Подумайте об этом так: один поток означает одну руку, которой можно что-то делать.</p><h2>Что такое асинхронная система</h2><p>В этой системе задачи выполняются независимо друг от друга.</p><p>Представьте, что для выполнения 10 задач у вас есть 10 рук. Таким образом, каждая рука может выполнять задачу независимо друг от друга и одновременно.</p><p>Посмотрите на GIF. Вы увидите, что каждое изображение загружается одновременно.</p><p>Все изображения загружаются в своем собственном темпе. Ни одно из них не ждёт загрузки других.</p><h2>Итак, в чём разница между синхронным и асинхронным JS</h2><p>В синхронной системе изображения находятся в одном потоке обработки. Одно изображение не может обогнать другое. Загрузка завершается по очереди. Если первое изображение заканчивает загрузку, останавливается и следующее изображение.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/f0465d2f-c982-4a7f-811a-4f22552a6cff.png" alt="" /></figure><p>В асинхронной системе изображения находятся на разных дорожках. Они закончат загрузку в своем собственном темпе. Никто ни перед кем не останавливается, если вдруг возникнет ошибка.</p><h2>Примеры синхронного и асинхронного кода</h2><p>Прежде чем начать наш проект, давайте рассмотрим несколько примеров.</p><h2>Пример синхронного кода</h2><p>Чтобы протестировать синхронную систему, напишите этот код на JavaScript:</p><p>Вот результат в консоли:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/ceb12adb-33c3-430f-83e2-9a5a35ec7018.png" alt="" /></figure><h2>Пример асинхронного кода</h2><p>Допустим, чтобы съесть мороженое, требуется две секунды. Теперь давайте протестируем асинхронную систему. Напишите приведенный ниже код на JavaScript.</p><p><b>Примечание:</b> Не волнуйтесь, функцию setTimeout() мы обсудим позже.</p><p>И вот результат в консоли:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/31dcd419-93dd-4069-9677-fc36e7e063c9.png" alt="" /></figure><p>Теперь, когда вы знаете разницу между синхронными и асинхронными операциями, давайте создадим наш магазин мороженого.</p><h2>Как настроить наш проект</h2><p>Для этого проекта вы можете просто открыть Codepen.io и начать кодить. Или вы можете сделать это в VS code или другом редакторе по вашему выбору.</p><p>Откройте раздел JavaScript, а затем откройте консоль разработчика. Мы напишем наш код и посмотрим результаты в консоли.</p><h2>Что такое обратные вызовы в JavaScript</h2><p>Когда вы вкладываете функцию в другую функцию в качестве аргумента, это называется обратным вызовом или callback.</p><p>Вот иллюстрация обратного вызова:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/497ec9eb-68e7-4941-baf3-754a92df3022.png" alt="" /></figure><p>Не волнуйтесь, через минуту мы увидим несколько примеров обратных вызовов.</p><h2>Зачем использовать обратные вызовы</h2><p>При выполнении сложной задачи мы разбиваем ее на более мелкие шаги. Чтобы установить связь между этими шагами по времени (необязательно) и порядку, мы используем обратные вызовы.</p><p>Взгляните на этот пример:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/b4a92cb1-d02b-456c-a473-dc9dfc86ae36.png" alt="" /></figure><p>Вот те небольшие шаги, которые необходимо сделать, чтобы приготовить мороженое.</p><p>Также обратите внимание, что в этом примере порядок действий и время имеют решающее значение. Вы не можете просто нарезать фрукты и подать мороженое.</p><p>В то же время, если предыдущий шаг не завершен, мы не можем перейти к следующему.</p><p>Магазин будет состоять из двух частей:</p><ol><li>В кладовой будут храниться все ингредиенты [Бэкенд].</li><li>Производить мороженое мы будем на нашей кухне [Фронтенд].</li></ol><h2>Создаём и сохраняем данные</h2><p>Теперь давайте создадим наши ингредиенты, то есть фрукты, внутри объекта.</p><p>Дополним наши ингредиенты вафельными стаканчиками, топпингами и прочим:</p><p>Весь бизнес зависит от того, что заказывает клиент. Как только мы получаем заказ, мы начинаем производство, а затем подаем мороженое. Итак, мы создадим две функции:</p><ol><li>order;</li><li>production.</li></ol><p>Вот как все это работает:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/9e5760e1-2fd4-44a1-ab9a-c8964d1eb2ee.png" alt="" /><figcaption>Получаем заказ, выбираем ингредиенты, готовим заказ и отдаём его.</figcaption></figure><p>Давайте создадим наши функции. Здесь мы будем использовать стрелочные функции:</p><p>Теперь давайте установим связь между этими двумя функциями с помощью обратного вызова, например, так:</p><h2>Давайте проведем небольшой тест</h2><p>Мы будем использовать функцию console.log() для проведения тестов, чтобы развеять все сомнения, которые могут возникнуть относительно того, как мы установили связь между двумя функциями.</p><p>Чтобы запустить тест, мы вызовем функцию order. И добавим в качестве аргумента вторую функцию с именем production.</p><p>Вот результат в нашей консоли:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/129b6e75-94fa-419a-bca4-eefedf0c8c54.png" alt="" /></figure><p>Сохраните этот код и удалите все, но не удаляйте нашу переменную stocks.</p><p>В первой функции передайте еще один аргумент, чтобы мы могли получить заказ с названием нужного фрукта:</p><p>Обозначим последовательность шагов и нужное время для их выполнения.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/29261dcc-4286-4889-a0ce-4c9ceb046019.png" alt="" /></figure><p>На этой схеме видно, что шаг 1 – размещение заказа, который занимает 2 секунды. Затем шаг 2 – нарезать фрукты (2 секунды), шаг 3 – добавить воду и лед (1 секунда), шаг 4 – запустить машину (1 секунда), шаг 5 – выбрать контейнер (2 секунды), шаг 6 – выбрать начинку (3 секунды) и шаг 7 – подать мороженое, что занимает 2 секунды.</p><p>Для определения нужного времени отлично подходит функция setTimeout(), так как она использует callback, принимая функцию в качестве аргумента.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/086047bb-8ce7-4b02-8489-598963e27834.png" alt="" /></figure><p>Теперь давайте выберем наши фрукты и воспользуемся этой функцией:</p><p>Вот результат в консоли. Обратите внимание, что результат отображается через 2 секунды.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/28ae9675-3097-4bfc-b0c9-3b71f1a933be.png" alt="" /></figure><p>Если вам интересно, как мы выбрали именно клубнику, вот код с форматом:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/8c4bff84-26d4-4c48-8755-d09dc277d045.png" alt="" /></figure><p>Ничего не удаляйте. Теперь мы начнем писать функцию для производства мороженого. Мы опять будем использовать стрелочные функции:</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/6b110961-5abe-4da0-841c-47ee74e2d306.png" alt="" /></figure><p>Добавим еще одну функцию в setTimeout, чтобы нарезать фрукты.</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/e091840e-5ac1-44bf-b1ee-c5df61171a70.png" alt="" /></figure><p>Давайте завершим полный процесс производства мороженого, вложив новые функции внутрь существующих функций – это и есть обратный вызов, помните?</p><figure><img src="https://media.tproger.ru/uploads/2023/05/24d42461-0c01-4c3a-ae22-09237aac8c81.png" alt="" /></figure><p>Смущены тем, как всё выглядит? Это ад обратных вызовов. ? Они всегда выглядят примерно вот так:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/5e3bf59e-8b3b-458b-9fc6-b3c62aecc8e4.png" alt="" /></figure><p>Как же сделать так, чтобы код выглядел красивым?</p><h2>Как использовать Promises, чтобы избежать ада обратного вызова</h2><p>Promises были изобретены для избавления от ада обратных вызовов и для лучшей обработки наших задач.</p><p>Вот, как они выглядят:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/a1f944ef-2174-4ed6-a65c-5ec758cbcb56.png" alt="" /></figure><p>Разберём, как они работают.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/a0be9172-45df-49fd-9547-aebfc5175305.png" alt="" /></figure><p>Как показано на графиках выше, promise имеет три состояния:</p><ol><li>Ожидание. Это начальная стадия. Здесь ничего не происходит. Подумайте об этом так: ваш клиент не торопится делать заказ. Он ещё ничего не заказал.</li><li>Решено. Это означает, что ваш клиент получил свою еду и доволен.</li><li>Отклонено. Это означает, что ваш клиент не получил свой заказ и покинул ресторан.</li></ol><p>Давайте применим обещания в нашем примере с производством мороженого.</p><p>Сначала нам нужно понять еще четыре вещи:</p><ol><li>Взаимосвязь между временем и работой.</li><li>Как работает цепочка Promise.</li><li>Как работает обработка ошибок.</li><li>Как работает обработчик .finally</li></ol><p>Разберем каждую из этих концепций по очереди, делая небольшие шаги.</p><h3>Взаимосвязь между временем и работой</h3><p>Если вы помните, это наши шаги и время, которое занимает каждый шаг, чтобы сделать мороженое.</p><p>Чтобы это произошло, давайте создадим переменную в JavaScript:</p><p>Теперь создайте функцию с именем order и передайте ей два аргумента с именами time, work:</p><p>Теперь мы дадим обещание (Promise) нашему клиенту: “Мы подадим вам мороженое”:</p><p>Наше обещание состоит из 2 частей:</p><ol><li>Решено [мороженое доставлено].</li><li>Отклонено [клиент не получил мороженое].</li></ol><p>То есть Promise состоит из Resolve и Reject.</p><p>Давайте добавим коэффициенты времени и работы внутри нашего promise с помощью функции setTimeout() внутри оператора if.</p><p><b>Примечание:</b> В реальной жизни вы можете обойтись и без фактора времени. Это зависит от требований проекта.</p><p>Теперь мы используем нашу только что созданную функцию для запуска производства.</p><p>Результат после двух секунд ожидания:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/a1322b13-c393-49dd-aa29-3855b2450afb.png" alt="" /></figure><h3>Цепочки обещаний</h3><p>В этом методе мы определяем, что нам нужно сделать, когда первая задача будет выполнена, используя обработчик .then.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/c9646a35-e424-4c3e-b298-e75017fdb403.png" alt="" /></figure><p>Обработчик .then возвращает promise, когда наше первое обещание будет разрешено.</p><p>Такой механизм похож на то, как вы даете кому-то инструкции. Вы говорите кому-то: “Сначала сделай это, потом сделай то, потом другое, потом…, потом…, потом…”, и так далее.</p><ol><li>Первая задача – это первоначальный Promise.</li><li>Остальные задачи возвращают наше обещание после того, как будет выполнена одна небольшая часть работы.</li></ol><p>Давайте реализуем это в нашем проекте.</p><p><b>Примечание:</b> не забудьте написать слово return внутри обработчика .then. В противном случае он не будет работать должным образом. Если вам интересно, зачем он нужен, попробуйте удалить return, когда мы закончим все шаги:</p><p>Вот результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/3d2b244d-454d-4bdc-8d49-0467eb25b3ea.png" alt="" /></figure><p>Используя ту же систему, закончим наш проект.</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/32b5f152-f0d1-4874-82cc-ff00d26b18ca.png" alt="" /></figure><h3>Обработка ошибок</h3><p>Нам нужен способ обработки ошибок, когда что-то идет не так. Чтобы отловить ошибки, давайте изменим нашу переменную на false.</p><p>Это означает, что наш магазин закрыт. Мы больше не продаем мороженое нашим клиентам.</p><p>Чтобы справиться с ошибкой, мы используем обработчик .catch. Как и .then, он также возвращает Promise, но только в том случае, если наше <b>первоначальное</b> обещание отклонено.</p><p>Итак, напоминаем:</p><ol><li>.then работает, когда Promise сработал как следует.</li><li>.catch работает, когда Promise отвергнут.</li></ol><p>Таким образом, между предыдущим обработчиком .then и обработчиком .catch не должно быть вообще ничего.</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/a9c38e8c-d521-44c9-b363-d02dead93346.png" alt="" /></figure><p>Что значит вывод после исполнения кода:</p><ol><li>Первое сообщение приходит от части reject() нашего Promise.</li><li>Второе сообщение приходит из обработчика .catch.</li></ol><h3>Как использовать обработчик .finally()</h3><p>Существует обработчик finally, который работает независимо от того, был ли наш Promise выполнен или отклонен.</p><p>Например: независимо от того, обслужили ли мы хотя бы одного клиента или 100 клиентов, наш магазин закроется в конце дня.</p><p>Вот пример такого кода:</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/ac6617a9-2655-4dff-a44e-1f8ab142716f.png" alt="" /></figure><h2>Как работает Async / Await в JavaScript</h2><p>Предполагается, что это лучший способ написания Promise, который помогает нам сохранять код простым и чистым.</p><p>Все, что вам нужно сделать, это написать слово async перед любой обычной функцией, и она станет обещанием.</p><h3>Promises против Async/Await на JavaScript</h3><p>До появления async/await для выполнения обещания мы писали следующее:</p><p>Теперь используем async:</p><h3>Как использовать ключевые слова Try и Catch</h3><p>Мы используем try для выполнения кода, а catch – для отлова ошибок. Это та же концепция, которую мы видели при рассмотрении обещаний.</p><p>Давайте сравним, как это работает.</p><p>В обещаниях мы использовали resolve и reject:</p><p>Когда мы используем async/await, мы используем этот формат:</p><p>Теперь, надеемся, вы понимаете разницу между Promises и Async/Await.</p><h3>Как использовать Await в JavaScript</h3><p>Ключевое слово await заставляет JavaScript ждать, пока Promise выполнится и вернет результат.</p><p>Вернемся к нашему магазину мороженого. Мы не знаем, какой топпинг предпочтет покупатель: шоколад или арахис. Нам нужно остановить машину и спросить клиента, чего бы он хотел.</p><p>Заметьте, что мы остановили только кухню, но персонал вне кухни продолжает делать такие вещи, как:</p><ol><li>мытье посуды;</li><li>уборка столов;</li><li>приём заказов, и т.д.</li></ol><p>Давайте создадим Promise, чтобы спросить, какой топпинг использовать. Процесс занимает три секунды.</p><p>Теперь давайте сначала создадим функцию для кухни с async.</p><p>Давайте добавим другие задачи ниже kitchen().</p><p>Вот результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/b2a2bc32-340a-4af7-a7c1-68ef29049ca3.png" alt="" /></figure><p>Мы буквально выходим из кухни, чтобы спросить клиента: “Какой топпинг вы предпочитаете?”. В это время другие задачи не заканчиваются.</p><p>Как только мы узнаем о выборе топпинга, мы возвращаемся на кухню и заканчиваем заказ.</p><p>При использовании Async/Await вы также можете использовать обработчики .then, .catch и .finally, которые являются основной частью Promises.</p><h2>Запускаем магазин мороженого</h2><p>Для этого создадим две функции:</p><ol><li>kitchen для приготовления мороженого;</li><li>time для назначения количества времени, которое займет каждая небольшая задача.</li></ol><p>Сначала создадим функцию time:</p><p>Теперь создадим кухню:</p><p>Проверим, работает ли наша кухня:</p><p>Вот, что вы должны увидеть, если магазин считается открытым:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/da6aaf07-45f9-46b7-9b37-86ecb0a4ff49.png" alt="" /></figure><p>А вот, что будет, если магазин закрыт.</p><figure><img src="https://media.tproger.ru/uploads/2023/05/dbde8d43-db17-46e3-8928-a6f4504a9495.png" alt="" /></figure><p>Завершим наш проект. Для этого вспомним, какие задачи должны выполняться в магазине:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/f43e9a96-4cd0-4ce4-a27c-b3f349356655.png" alt="" /></figure><p>Открываем магазин:</p><p>Добавляем все функции для нашей кухни:</p><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2023/05/274f3312-0af8-498c-bd20-2b3e1efa246a.png" alt="" /></figure><h2>Заключение</h2><p>Поздравляем, вы дочитали до конца! В этой статье вы изучили:</p><ol><li>Разницу между синхронными и асинхронными системами.</li><li>Механизмы асинхронного JavaScript с использованием обратных вызовов, обещаний и Async/Await.</li></ol><p>Благодарим за то, что дочитали до конца!</p>]]></content:encoded>
    </item>
    <item>
      <title>Решаем популярные задачи с асинхронным кодом на JavaScript: часть вторая</title>
      <link>https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-vtoraja</link>
      <comments>https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-vtoraja?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Oleg Sabitov]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-vtoraja</guid>
      <description><![CDATA[<p>Продолжаем разбирать задачи с асинхронным кодом в JS. В этой статье рассмотрим порядок попадания задач в Event Loop и оптимизацию кода.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-vtoraja">Решаем популярные задачи с асинхронным кодом на JavaScript: часть вторая</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Задачи умеренной сложности]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 02 Feb 2023 12:51:17 GMT</pubDate>
      <content:encoded><![CDATA[<p>В первой части текста мы вспомнили, как устроен цикл событий и разобрали несколько простых задач на асинхронное программирование на JS. В этой статье преподаватель Elbrus Bootcamp Денис Образцов разберёт более сложные примеры на порядок попадания задач в Event Loop и оптимизацию кода.</p><h2>Задача первая</h2><p>Разберём задачу подробно. В первой строке объявляется переменная без присвоения значения, после которой создаётся promise и вывод в консоль значения a. Все перечисленные строки — это синхронный код, поскольку сам по себе promise не является асинхронным.</p><p>После этого ей присваивается значение: a = 25. Следующим шагом появляется таймер setTimeout, внутри которого вывод значения переменной в консоль и функция resolve, в которую вкладывается переменная ‘a’ для успешного исполнения promise c задержкой в 100 миллисекунд.</p><p>Если предыдущий таймер находился внутри promise, то второй setTimeout написан как отдельная функция. Здесь переменной ‘a’ присваивается новое значение, которое выводится в консоль с задержкой в 100 миллисекунд. На последних трёх строчках — обработка promise через then, которая завершается выводом консоли. Очередность вывода и переменная ‘a’ будут выглядеть следующим образом:</p><p>С точки зрения очерёдности самая запутанная часть задачи — promise, внутри которого находится setTimeout. Базово таймер относится к макрозадачам, которые попадают в Call Stack последними и выполняются в последнюю очередь. Поскольку в этой задаче он находится внутри promise — микрозадачи, которая выполняется раньше, — в вывод он попадёт третьим. Сразу после того, как отработал весь синхронный код.</p><p>Если говорить о значениях, которые выводятся в консоль, то в первой строке будет undefined, поскольку никакое значение на этом этапе переменной не присвоено. Это произойдёт только в следующей строке. Консоль, которая выполняется во вторую очередь, выдаст значение 25, так как она расположена в конце кода, и к моменту её появления значение переменной уже присвоено. В следующих выводах в консоль выводится значение переменной.</p><figure><img src="https://media.tproger.ru/uploads/2023/04/3a49b6fa-1bd6-4cc1-ab55-fb5609b32993.jpg" alt="" /></figure><p>Больше задач и историй студентов — в нашем Telegram-канале @Elbrus Bootcamp</p><h2>Задача вторая</h2><p>В этой задаче появляется функция async. Прежде чем приступить к разбору, вспомним, для чего она нужна.</p><p>Функция async позволяет работать с асинхронным кодом так, будто он синхронный. Синхронный код не работает с асинхронным, но в пределах функции async возможно сделать вид, что так можно. Для этой задачи важно отметить, что любая async-функция сразу же возвращает promise. В паре с async идёт ключевое слово await, которое буквально означает «дождись». Сама по себе async-функция, как и promise — не является асинхронной.</p><p>Перейдём к разбору кода. В первой строке находится функция resolveAfter2Seconds, которая принимает аргумент x, выводит его значение в консоль и возвращает его в promise с задержкой в пять секунд.</p><p>Далее следует функция async. Внутри неё — синхронная консоль, за которой следуют две асинхронные функции, которые «дожидаются» своего выполнения через await. Результат их поочередного выполнения попадает в promise, а аргумент x приобретает значение сначала ‘a’, а затем — ‘b’. Затем выводится результат Bye.</p><p>В последней строчке функции указан вывод результата. Так как любая async возвращает promise, в return не удастся получить конкретного результата. Вместо него мы получим promise pending — ожидание. Then в последней строчке — обработчик этого pending.</p><p>В результате порядок выполнения будет следующим:</p><p>Этот код работает медленно: чем больше внутри async-функции add1 вызовов функций resolveAfter2Seconds с ожиданием (await), тем больше времени займёт его выполнение. Если вызовов два, то он будет выполняться 10 секунд. Если их будет 10, ждать придётся почти минуту.</p><p>Попробуем переписать код и сократить время ожидания. Поставим все вызовы в очередь без await, чтобы они выполнялись все одновременно за пять секунд:</p><p>Теперь мы дожидаемся выполнения всех функций уже в конце и только после этого показываем конечный результат. В результате оптимизации ждать придётся не 20 секунд, а всего пять.</p><p>Реклама ООО “Эльбрус Буткемп”</p>]]></content:encoded>
    </item>
    <item>
      <title>Решаем популярные задачи с асинхронным кодом на JavaScript: часть первая</title>
      <link>https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-pervaja</link>
      <comments>https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-pervaja?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Oleg Sabitov]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-pervaja</guid>
      <description><![CDATA[<p>В статье разобрали популярные задачи с асинхронным кодом, которые могут попасться начинающему Frontend-разработчику на собеседованиях.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/reshaem-populjarnye-zadachi-s-asinhronnym-kodom-na-javascript-chast-pervaja">Решаем популярные задачи с асинхронным кодом на JavaScript: часть первая</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Задачи умеренной сложности]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 25 Jan 2023 14:43:29 GMT</pubDate>
      <content:encoded><![CDATA[<p><b>На собеседованиях начинающим Frontend-разработчикам часто попадаются задачи на асинхронный код. Преподаватель Elbrus Bootcamp Денис Образцов выбрал несколько популярных задач, с которыми наши выпускники часто сталкиваются на интервью, и разобрал логику их решения. </b></p><p><i>В первой части текста вспомним, как устроен цикл событий и разберём несколько базовых задач на логику и внимательность. Во второй части перейдём к более сложным примерам на порядок попадания задач в Event Loop и оптимизацию кода.</i></p><h2>Как устроен цикл событий</h2><p>Машина читает код дважды: сначала в память компьютера записываются переменные, потом происходит непосредственное выполнение кода. Часто задач в коде несколько: они попадают в цикл событий (Event Loop) и выполняются в определённой последовательности.</p><p>Последовательность задаётся типом кода: синхронным или асинхронным. В случае с синхронным кодом все задачи попадают сразу в Call Stack и выполняются по очереди.</p><p>Асинхронный код используется, например, когда программе нужно обратиться к базе данных или другому внешнему источнику информации. Этот процесс можно сравнить с телефонным звонком: когда вы звоните кому-то, вы заранее не знаете, когда вам ответят — после первого гудка, после пятого или вообще не возьмут трубку.</p><p>С асинхронным кодом сложнее: во-первых, он всегда выполняется после синхронного, а во-вторых, делится ещё на две очереди — макро- и микрозадачи.</p><p>Микрозадачи — в основном, промисы, которые выполняются в первую очередь. Большие задачи (например, таймеры, AJAX-запросы) попадают в самый конец стека и выполняются последними.</p><p>Теперь, когда мы вспомнили теорию, перейдём к разбору задач на понимание асинхронного кода, которые могут попасться на собеседовании. Все задачи рассчитаны на джунов и собраны командой Elbrus Bootcamp на реальных интервью.</p><figure><img src="https://media.tproger.ru/uploads/2023/04/3a49b6fa-1bd6-4cc1-ab55-fb5609b32993.jpg" alt="" /></figure><p>Больше задач и историй студентов — в нашем Telegram-канале @Elbrus Bootcamp</p><h2>Задача первая</h2><p>Прежде чем разбирать код, рассмотрим пример, к которому мы будем возвращаться на протяжении всей статьи. Он поможет глубже понять принцип работы асинхронного кода.</p><p>Представьте, что вы пришли в фастфуд, сделали заказ, получили специальный пульт и ждёте, пока пульт завибрирует и можно будет пойти и забрать заказ.</p><p>В первой строке кода мы видим promise — это специальный объект, который даёт обещание, что в будущем будет выполнено то или иное действие. Promise выступает аналогом такого пульта и в данном случае обещает уведомить не о готовности заказа, а об ошибке, если она возникнет.</p><p>Под promise прописан вариант развития событий — reject, который выводит в консоль сообщение «Всё сломалось» в случае, если что-то пошло не так. Второй, положительный, вариант resolve в этой задаче не указан.</p><p>Вернёмся к примеру с фастфудом: вы сели за столик и ждёте заказ. Через некоторое время пульт завибрировал. Дальше может быть несколько вариантов развития событий: вы успешно получите заказ или кассир позовёт вас сообщить, что какого-то ингредиента нет и блюдо не смогут приготовить. На этот случай и нужны resolve и reject.</p><p>Следующий шаг — встать и подойти к стойке. За него отвечают обработчики .catch, которые в коде идут в цепочке друг за другом. Это важный момент: между обработчиками нет точки с запятой, и цепочка идёт сразу после объявления переменной ‘p’, поэтому выполняется только первый .catch. Второй выполняет те же действия и не срабатывает.</p><p>Это сравнительно простая задача: в ней нет смешивания синхронного и асинхронного кода. В консоли мы получим результат выполнения promise, а затем — вывод первого .catch.</p><h2>Дополнение первое</h2><p>Здесь происходит то же самое, что и в базовом варианте задачи, но с исключениями. Есть два обращения к константе ‘p2’, нет цепочки, между .catch появилась точка с запятой, поэтому в консоль выводится результат обоих обработчиков.</p><p>Стоит отметить, что смысла в этом немного: обработчики отлавливают одну и ту же ошибку. Но эта задача скорее на внимательность, чем на логику.</p><h2>Дополнение второе</h2><p>В этой версии задачи есть then. Здесь это обработчик положительного результата (resolve), который не выполняет никакую функцию, в этом коде он бесполезен. В тексте задачи по-прежнему упоминается только негативное развитие событий. Поэтому вывод в консоль будет тот же, что и в предыдущей задаче.</p><h2>Задача два</h2><p>Разберём текст задачи. В первой строчке указан таймер setTimeout с нулевой задержкой, следом идут два promise: c пустой функцией обработки положительного ответа и без функции.</p><p>Здесь then — обработчик первого promise, который получает результат выполнения resolve. В последних строчках — консоль завершения и консоль, которая выводит результат выполнения второго promise.</p><p>Вспомним, в каком порядке код попадает в Call Stack. В первую очередь выполняется синхронный код: console.log или promise. По дефолту они не асинхронные, пока вы не сделаете их таковыми (например, добавите .catch или .then).</p><p>Таким образом, вывод консоли будет иметь следующий порядок:</p><p>Строки с консолями внутри promise выполнятся в первую очередь, поскольку в них нет ничего асинхронного, так как promise сам по себе изначально синхронный. Затем выполняются синхронная консоль ‘End’ и консоль, в которой показывается второй promise, находящийся в стадии ожидания ().</p><p>Далее выполняется .then. В базовом варианте promise выполняется синхронно. Только после того, как весь синхронный код отработал, выполняется его асинхронный обработчик. В последнюю очередь выполнится макрозадача с setTimeout.</p><h2>Задача три</h2><p>В этой задаче первый и последний console.log синхронные, поэтому они выполнятся сразу. Следом идёт promise с двумя обработчиками, которые выстроены в цепочку и выполняются друг за другом. Обратите внимание, что между ними нет точки с запятой. В последнюю очередь выполняется setTimeout, поскольку это макрозадача.</p><p>Эти задачи рассчитаны на базовое понимание работы асинхронного кода. В следующей части статьи разберём более сложные кейсы и оптимизируем скорость выполнения задач в Call Stack.</p><p>Реклама ООО “Эльбрус Буткемп”</p>]]></content:encoded>
    </item>
    <item>
      <title>Углубляемся в JavaScript: всё ли может async/await, или когда использовать Promise</title>
      <link>https://tproger.ru/articles/uglubljaemsja-v-javascript-vsjo-li-mozhet-async-await-ili-kogda-ispolzovat-promise</link>
      <comments>https://tproger.ru/articles/uglubljaemsja-v-javascript-vsjo-li-mozhet-async-await-ili-kogda-ispolzovat-promise?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/uglubljaemsja-v-javascript-vsjo-li-mozhet-async-await-ili-kogda-ispolzovat-promise</guid>
      <description><![CDATA[<p>Сравнение возможностей async/await и промисов в JavaScript, их плюсов и минусов, а также ситуаций, в которых уместнее каждый из подходов.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/uglubljaemsja-v-javascript-vsjo-li-mozhet-async-await-ili-kogda-ispolzovat-promise">Углубляемся в JavaScript: всё ли может async/await, или когда использовать Promise</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 02 Feb 2021 06:19:34 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Что такое async/await и promise?</h2><p>Прежде чем ответить на поставленный вопрос, нам необходимо узнать немного теории.</p><p>Асинхронность меняет сложившуюся парадигму последовательного кода. Последовательность — когда только одна конкретная операция происходит в данный момент времени. Если функция зависит от результата выполнения другой функции, то она должна дождаться пока прошлая функция не завершит свою работу. Для пользователя это значит состояние вечного «ждуна».</p><p>Асинхронность нужна нам, чтобы делать несколько операций и функций параллельно. Асинхронное программирование — это инструмент для оптимизации высоконагруженных сайтов с долгими и частыми ожиданиями обратной связи. Например, когда одна функция создаёт canvas на странице, другая функция может подготовить данные необходимые для отрисовки внутри canvas. Ещё пример, когда пользователь кладет товар в корзину ему не обязательно ждать ответа сервера, мы заранее можем показать анимацию добавления товара в корзину, а всю остальную логику проверок сделать после ответа сервера, не блокируя интерфейс пользователю.</p><p>На самом деле с точки зрения машинного кода, async/await и промисы это абсолютно то же самое. Но мы то с вами люди, и нам важен синтаксис. И разница в синтаксисе настолько существенна, что разделила разработчиков на два лагеря. Любители колбэков выбрали Promise, а не любители цепочек выбрали async/await.</p><p>Async/await — синтаксис работающий с промисами, придуман как альтернатива синтаксису промисов. Используя async, можно полностью избежать использования цепочек промисов с помощью await. Async создает Promise. А await ждет выполнения промиса.</p><p>Promise — обертка (класс, для простоты понимания) для отложенных и асинхронных вычислений. Ожидает выполнения колбэк функций и никак иначе. Есть два колбэка: один заявляет об успешном выполнении, другой об ошибке. Promise может находиться в трёх состояниях: ожидание (pending), исполнено (fulfilled), отклонено (rejected). Промис начинает выполняться когда мы вызываем метод .then.</p><p>Давайте посмотрим практические маленькие примеры синтаксиса.</p><p>Пример 1:</p><p>Пример 2:</p><p>Пример 3:</p><p>Так как async является надстройкой над промисами, то мы можем смешивать код, например так:</p><p>или так</p><h2>Плюсы и минусы в теории</h2><p>Async/await<br />Плюсы</p><ul><li>Удобство и простота чтения</li><li>Возможность использования последовательного стиля программирования</li></ul><p>Минусы</p><ul><li>Легко наткнуться на избыточное ожидание последовательного кода. Для истинной параллельности нужно модифицировать код.</li><li>Неочевидность возвращаемых значений try…catch.</li></ul><p>Promise<br />Плюсы</p><ul><li>Использует традиционный подход колбэков.</li><li>Данные с ошибками и данные с успешным результатом операции однозначно понимаемы.</li><li>Возможность использовать Promise.all без оглядки на синтаксис.</li><li>Оповещения Promise.resolve и Promise.reject доступны везде.</li><li>Наглядное использование метода Promise.finally.</li></ul><p>Минусы</p><ul><li>При неправильном использовании возможно создание слишком глубоких использований цепочек .then</li></ul><p>На примерах выше видно, что Promise субъективно является более чистым кодом. Более того я заранее заложил одну противную пакость в примерах, о которой расскажу позже. Эта особенность не позволяет выбранному нами синтаксису использовать асинхронность в полной мере. Кто её нашел сходу, может дальше не читать 🙂</p><h2>Вера в обещание</h2><p>Мое знакомство с асинхронным js-кодом началось с библиотеки «КриптоПро ЭЦП Browser plug-in». Те, кто сталкивался с данной библиотекой, должны меня понять, у меня не было выбора, я искренне влюбился в промисы ? Она вся утыкана промисами. И первой техникой, которой пришлось овладеть, были .then и .catch. Порой вложенность кода составляла 10-15 уровней .then. Спустя годы я понимаю почему разработчикам плагина пришлось так поступить, но она прекрасна в своей ужасности.</p><p>Шло время, навыки оттачивались, и с тех пор я всегда пишу js-код на промисах.</p><p>А теперь о пакости.</p><h2>Сложный кейс с промисами, и главное преимущество промисов — колбеки</h2><p>Попалась мне интереснейшая задача «Платежная система отвечает об успешной оплате не сразу, поэтому придется слать несколько запросов в течение 30 секунд, при этом держать пользователя в режиме прелоадера, при этом если оплата пройдет раньше чем 30 секунд, то из цикла нужно выйти и отключить прелоадер, и если за 30 секунд ответа не получено, то показать ошибку».</p><p>Архитектура:</p><ol><li>Интервал запросов к серверу 1 секунда.</li><li>Необходим один большой (глобальный) промис, чтобы было удобно отключить прелоадер.</li><li>До входа в асинхронный код нужно включить прелоадер.</li><li>Внутри асинхронного кода должно произойти «нечто ужасное» без потери читабельности.</li><li>Отключение прелоадера должно происходить в финальном коде, независимо от того, успешная оплата, или ошибка, и независимо от того, сколько промисов будет использоваться в асинхронном коде.</li></ol><p>Для удобства сопоставления алгоритма и архитектуры код совсем чуть-чуть упрощен, и совпадает с оригинальным на 90%.</p><p>Результат:</p><p>На данном примере видно как используются колбэки — у нас есть полный простор в передаче ошибок в родительский промис, их множественный вызов в разных местах, когда нам необходимо. И также максимальный простор для выбора момента уведомления «родителя» об успешном окончании, тем самым мы решаем 5 пункт из запланированной архитектуры.</p><h2>Требование заказчика</h2><p>История закончилась бы замечательно, если бы не одно «но». ТЗ требовало, чтобы весь код был написан на async/await. Глядя на код выше можно сказать, что это достаточно сложный кейс. Первое, что можно подумать: «Это невозможно! Ведь async/await не могут ждать колбека, они только выполняют код и ничего не ждут.»</p><p>Ну хорошо… требование заказчика — закон… переписываем.</p><p>Задача действительно оказалась не решаема на уровне async/await. Потому что async, await и timeout не работают в связке. Пришлось совсем чуть-чуть смешать два разных синтаксиса с помощью функции sleep. Хорошо это или плохо? Вопрос субъективный. Мы лишь в очередной раз убедились, что async/await является лишь надстройкой над промисами.</p><h2>Вывод</h2><p>Нет ничего хуже, чем смешение разных стилей написания кода на одном проекте. Поэтому выбирайте стайлгайд по асинхронному коду заранее. Описанный выше кейс — это редкость. И зачастую async/await будет достаточно. Но если вы чувствуете, что на проекте будут сложные кейсы и есть вероятность использования колбэков, то используйте изначально промисы, применение конструкторов Promise тоже редкость. Остальное дело вкуса.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как писать эффективный код на JavaScript с помощью Event Loop</title>
      <link>https://tproger.ru/blogs/effective-code-in-js-with-event-loop</link>
      <comments>https://tproger.ru/blogs/effective-code-in-js-with-event-loop?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/blogs/effective-code-in-js-with-event-loop</guid>
      <description><![CDATA[<p>Event Loop обрабатывает асинхронные функции JavaScript в одном потоке. Разработчику важно следить, чтобы синхронные операции не блокировали цикл.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/blogs/effective-code-in-js-with-event-loop">Как писать эффективный код на JavaScript с помощью Event Loop</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Блоги]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 20 Sep 2019 13:50:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Препарирует принципы работы Event Loop Евгений, старший разработчик Noveo</p><p>Event Loop (цикл событий) — один из важнейших аспектов в JavaScript, знание которого позволяет писать более эффективный код. В статье мы рассмотрим, как работает основной поток в JavaScript и как он обрабатывает асинхронные функции.</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image5-1.jpg" alt="" /></figure><p>Долгое время я писал код на JavaScript, не до конца понимая, как он работает под капотом. В принципе, для того чтобы кодить на JavaScript, знать принципы его работы изнутри и не нужно, но это сделает ваш код лучше и позволит взглянуть на некоторые вещи в языке под другим углом.</p><p>Весь код в JavaScript выполняется в одном потоке, т. е. за один раз может обрабатываться только что-то одно. С одной стороны, это полезное ограничение, так как оно позволяет нам не задумываться об особенностях работы с параллелизмом. С другой стороны, мы постоянно должны контролировать код и заботиться о том, чтобы синхронные операции (бесконечные циклы, запрос данных по сети) не блокировали наш поток.</p><p>В большинстве браузеров у каждой вкладки свой цикл событий, это позволяет изолировать вкладки друг от друга, и если одна зависнет, другие продолжат работать. В противном случае одна зависшая операция могла бы нарушить работу всего браузера во всех вкладках.</p><p>По факту окружение может одновременно управлять большим количеством «циклов событий» для обработки API-запросов. WebWorkers также имеют свой цикл событий.</p><p>JavaScript-разработчик должен знать, что его код всегда выполняется в одном цикле событий, и следить за тем, чтобы не заблокировать его.</p><h2>Блокирование Event Loop</h2><p>Любой код, который будет долго выполняться прежде чем вернёт управление в основной поток, блокирует выполнение любого JavaScript-кода на странице. При этом также блокируется пользовательский интерфейс, и пользователь не может с ним взаимодействовать (кликать, прокручивать страницу и т. д.).</p><p>Почти все операции ввода/вывода в JavaScript являются неблокирующими — сетевые запросы, операции с файловой системой в Node.js и т. д. Исключением являются блокирующие операции, и именно поэтому в JavaScript так популярны обратные вызовы (callbacks), а в последнее время всё чаще начинают использовать Promise и async/await.</p><h2>Стек вызовов</h2><p>Стек вызовов — это очередь LIFO (Last In, First Out).</p><p>Цикл событий непрерывно обрабатывает стек вызовов в поиске функции, которая должна быть обработана. При этом он добавляет любой найденный вызов функции в стек вызовов и выполняет каждый по порядку.</p><p>Если вы знакомы со стеком вызовов в отладчике или консоли браузера, то пример далее будет вам понятен. Браузер ищет имена функций в стеке вызовов, чтобы сообщить вам, какая функция инициирует текущий вызов:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image12.png" alt="" /></figure><h2>Примеры работы с Event Loop</h2><p>На небольшом примере мы рассмотрим, как работает Event Loop:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image2-2.png" alt="" /></figure><p>После выполнения этот код выведет:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image3-1.png" alt="" /></figure><p>В принципе, как и ожидалось.</p><p>Давайте подробно разберём, как этот код обрабатывается через Event Loop. Когда код выполняется, первым вызывается foo(), внутри foo() первой вызывается bar(), а затем baz().</p><p>В этот момент стек вызовов выглядит так:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image4-3.png" alt="" /></figure><p>Цикл обработки событий на каждой итерации проверяет, есть ли в стеке вызовы, и если да, выполняет их:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image7-1.png" alt="" /></figure><p>Этот процесс продолжается до тех пор, пока стек не станет пустым.</p><h2>Порядок выполнения функций</h2><p>В примере выше нет ничего специфичного: JavaScript анализирует код и определяет порядок вызова функций.</p><p>Давайте посмотрим, как мы можем изменить порядок вызова функций, сделав так, что определённая функция будет вызвана последней. Для этого мы выполним вызов нашей функции посредством browser API:</p><p>setTimeout(() =&gt; {}, 0);</p><p>Рассмотрим следующий пример:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image6-1.png" alt="" /></figure><p>Результат выполнения этого кода для некоторых может стать неожиданным:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image9.png" alt="" /></figure><p>Когда этот код выполняется, сначала вызывается foo(). Внутри foo() мы сначала вызываем setTimeout, передавая bar в качестве аргумента, а временным интервалом указываем 0, чтобы вызов произошёл настолько быстро, насколько это возможно. Затем мы вызываем baz().</p><p>На этом этапе стек вызовов выглядит следующим образом:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image10.png" alt="" /></figure><p>Порядок вызова функций в этом случае будет выглядеть так:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image8-1.png" alt="" /></figure><p>Далее мы рассмотрим, почему так происходит.</p><h2>Очередь сообщений (The Message Queue)</h2><p>Когда вызывается setTimeout, браузер или Node.js запускают таймер. Когда время таймера истекает (в нашем случае это произойдёт немедленно, так как мы указали 0 в качестве временного интервала), наша callback-функция будет помещена в очередь сообщений.</p><p>В очередь сообщений также помещаются события, инициируемые пользователем (клик, нажатие клавиш на клавиатуре, движение мышки, сетевые запросы, такие как fetch), а также события, генерируемые DOM, например onLoad.</p><p>В первую очередь Event Loop обрабатывает всё, что содержится в стеке вызовов, и только после этого начинает обрабатывать содержимое очереди.</p><p>Нам не нужно ждать, пока такие функции, как setTimeout, fetch или другие выполняют свою работу, поскольку они предоставляются браузером и живут в своих потоках. Например, если вы установите время ожидания setTimeout равным 2 секундам, вам не придётся ждать 2 секунды — ожидание происходит в отдельном потоке.</p><h2>Очередь заданий (ES6 Job Queue)</h2><p>ECMAScript 2015 представил концепцию очереди заданий, которая используется в Promises (также представлена в ES6/ES2015). Это способ выполнить результат асинхронной функции как можно скорее, а не помещать его в конец стека вызовов. Обещания, которые разрешаются до завершения текущей функции, будут выполняться сразу после текущей функции.</p><p>Это своего рода VIP-очередь, обработка которой имеет приоритет по отношению к обычной очереди.</p><p>Пример:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image11.png" alt="" /></figure><p>Результат:</p><figure><img src="https://media.tproger.ru/uploads/2019/09/image1-2.png" alt="" /></figure><p>В этом большая разница между Promises (а также async/await, который построен на Promises) и привычными асинхронными функциями через setTimeout() или другие API-платформы.</p><p>Надеемся, статья поможет вам разобраться с работой Event Loop в JavaScript, включая работу с потоками, очередями событий и API браузера. Для наглядности мы рассмотрели несколько примеров и то, как их можно оптимизировать с точки зрения производительности.</p>]]></content:encoded>
    </item>
    <item>
      <title>Разбираемся с Async/Await в JavaScript на примерах</title>
      <link>https://tproger.ru/translations/understanding-async-await-in-javascript</link>
      <comments>https://tproger.ru/translations/understanding-async-await-in-javascript?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Александр Ланский]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/understanding-async-await-in-javascript</guid>
      <description><![CDATA[<p>Асинхронное программирование в JavaScript на примерах кода: чем неудобны вложенные колбэки, ведущие к Callback Hell, и что даёт Async/Await.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/understanding-async-await-in-javascript">Разбираемся с Async/Await в JavaScript на примерах</a>»</p>]]></description>
      <category><![CDATA[JavaScript]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 25 Jun 2019 11:34:19 GMT</pubDate>
      <content:encoded><![CDATA[<p>Callback — это не что-то замысловатое или особенное, а просто функция, вызов которой отложен на неопределённое время. Благодаря асинхронному характеру JavaScript, обратные вызовы нужны были везде, где результат не может быть получен сразу.</p><p>Ниже приведён пример асинхронного чтения файла на Node.js:</p><p>Проблемы начинаются, когда нужно выполнить несколько асинхронных операций. Просто представьте себе подобный сценарий:</p><ul><li>Выполняется запрос в БД на некого пользователя Arfat. Нужно считать его поле profile_img_url и загрузить соответствующее изображение с сервера someServer.ru.</li><li>После загрузки изображения необходимо его конвертировать, допустим из PNG в JPEG.</li><li>В случае успешной конвертации нужно отправить письмо на почту пользователя.</li><li>Это событие нужно занести в файл transformations.log и указать дату.</li></ul><p>Обратите внимание на вложенность обратных вызовов и пирамиду из }) в конце. Подобные случаи принято называть <a href="http://callbackhell.com/">Callback Hell</a> или <a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_%28programming%29">Pyramid of Doom</a>. Вот основные недостатки:</p><ul><li>Такой код сложно читать.</li><li>В таком коде сложно обрабатывать ошибки и одновременно сохранять его «качество».</li></ul><p>Для решения этой проблемы в JavaScript были придуманы <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">промисы</a> (англ. promises). Теперь глубокую вложенность коллбэков можно заменить ключевым словом then:</p><p>Код стал читаться сверху вниз, а не слева направо, как это было в случае с обратными вызовами. Это плюс к читаемости. Однако и у промисов есть свои проблемы:</p><ul><li>Всё ещё нужно работать с кучей .then.</li><li>Вместо обычного try/catch нужно использовать .catch для обработки всех ошибок.</li><li>Работа с несколькими промисами в цикле не всегда интуитивно понятна и местами сложна.</li></ul><p>В качестве демонстрации последнего пункта попробуйте выполнить такое задание:</p><p>Предположим, что у вас есть цикл for, который выводит последовательность чисел от 0 до 10 со случайным интервалом (от 0 до n секунд). Используя промисы нужно изменить цикл так, чтобы числа выводились в строгой последовательности от 0 до 10. К примеру, если вывод нуля занимает 6 секунд, а единицы 2 секунды, то единица должна дождаться вывода нуля и только потом начать свой отсчёт (чтобы соблюдать последовательность).</p><p>Стоит ли говорить, что в решении этой задачи нельзя использовать конструкцию async/await либо .sort функцию? Решение будет в конце.</p><h2>Async функции</h2><p>Добавление async-функций в ES2017 (ES8) сделало работу с промисами легче.</p><ul><li>Важно отметить, что async-функции работают поверх промисов.</li><li>Эти функции не являются принципиально другими концепциями.</li><li>Async-функции были задуманы как альтернатива коду, использующему промисы.</li><li>Используя конструкцию async/await, можно полностью избежать использование цепочек промисов.</li><li>С помощью async-функций возможно организовать работу с асинхронным кодом в синхронном стиле.</li></ul><p>Как видите, знание промисов всё же необходимо для понимания работы async/await.</p><h3>Синтаксис</h3><p>Синтаксис состоит из двух ключевых слов: async и await. Первое делает функцию асинхронной. Именно в таких функциях разрешается использование await. Использование await в любом другом случае вызовет ошибку.</p><p>Обратите внимание, что async вставляется в начале объявления функции, а в случае стрелочной функции — между знаком = и скобками.</p><p>Async-функции могут быть помещены в объект в качестве методов или же просто использоваться в объявлении класса.</p><p>Примечание Конструкторы класса и геттеры/сеттеры не могут быть асинхронными.</p><h3>Семантика и правила выполнения</h3><p>Async-функции похожи на обычные функции в JavaScript, за исключением нескольких вещей:</p><h4>Async-функции всегда возвращают промисы</h4><p>Функция fn возвращает строку 'hello'. Т. к. это асинхронная функция, значение строки обёртывается в промис (с помощью конструктора).</p><p>Код выше можно переписать и без использования async:</p><p>В таком случае, вместо async, код вручную возвращает промис.</p><h4>Тело асинхронной функции всегда обёртывается в новый промис</h4><p>Если возвращаемое значение является примитивом, async-функция возвращает это значение, обёрнутое в промис. Но если возвращаемое значение и есть объект промиса, его решение возвращается в новом промисе.</p><p>Что происходит, когда внутри асинхронной функции возникает какая-нибудь ошибка?</p><p>Если ошибка не будет обработана, foo() вернёт промис с реджектом. В таком случае вместо Promise.resolve вернётся Promise.reject, содержащий ошибку.</p><p>Суть async-функций в том, что что бы вы не возвращали, на выходе вы всегда будете получать промис.</p><h4>Асинхронные функции приостанавливаются при каждом await выражении</h4><p>await сказывается на выражениях. Если выражение является промисом, то async-функция будет приостановлена до тех пор, пока промис не выполнится. Если же выражение не является промисом, то оно конвертируется в промис через Promise.resolve и потом завершается.</p><p>Как работает fn функция?</p><ol><li>После вызова fn функции первая строка конвертируется из const a = await 9; в const a = await Promise.resolve(9);.</li><li>После использования await, выполнение функции приостанавливается, пока a не получит своё значение (в данном случае это 9).</li><li>delayAndGetRandom(1000) приостанавливает выполнение fn функции, пока не завершится сама (после 1 секунды). Это, фактически, можно назвать остановкой fn функции на 1 секунду.</li><li>Также delayAndGetRandom(1000) через resolve возвращает случайное значение, которое присваивается переменной b.</li><li>Случай с переменной c идентичен случаю переменной a. После этого опять происходит пауза на 1 секунду, но теперь delayAndGetRandom(1000) ничего не возвращает, т. к. этого не требуется.</li><li>Под конец эти значения считаются по формуле a + b * c. Результат обёртывается в промис с помощью Promise.resolve и возвращается функцией.</li></ol><p>Примечание Если такие паузы напоминают вам <a href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5">генераторы</a> в ES6, то на это есть <a href="https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5">свои причины</a>.</p><h2>Решение задачи</h2><p>Вот решение задачи, поставленной в начале статьи, с использованием async/await.</p><p>В функции finishMyTask используется await для ожидания результатов таких операций, как queryDatabase, sendEmail, logTaskInFile и т. д. Если сравнить это решение с решением, использовавшим промисы, то вы обратите внимание на их сходство. Однако версия с async/await упрощает синтаксические сложности. В этом способе нет кучи коллбэков и цепочек .then/.catch.</p><p>Вот то решение с выводом чисел. Тут есть два способа:</p><p>С использованием async-функций решение поставленной задачи упрощается до безобразия:</p><h2>Обработка ошибок</h2><p>Как было сказано выше, необработанные ошибки обёртываются в неудачный (rejected) промис. Но в async-функциях всё ещё можно использовать конструкцию try-catch для синхронной обработки ошибок.</p><p>canRejectOrReturn() — это асинхронная функция, которая будет удачно завершатся с 'Число подошло', либо неудачно завершаться с Error('Простите, число больше, чем нужно.').</p><p>Поскольку в коде выше ожидается выполнение canRejectOrReturn, то его собственное неудачное завершение вызовет исполнение блока catch. Поэтому функция foo завершится либо с undefined (т. к. в блоке try ничего не возвращается), либо с 'Ошибка обработана'. Поэтому у этой функции не будет неудачного завершения, т. к. try-catch блок будет обрабатывать  ошибку самой функции foo.</p><p>Вот другой пример:</p><p>Обратите внимание, что в коде выше из foo возвращается (без ожидания) canRejectOrReturn. foo завершится либо с 'число подошло', либо с реджектом Простите, число больше, чем нужно.‘). Блок catch никогда не будет исполняться.</p><p>Это происходит из-за того, что foo возвращает промис, который передан от canRejectOrReturn. Следовательно, решение функции foo становится решением canRejectOrReturn. Такой код можно представить всего в двух строках:</p><p>Вот что получится, если использовать await и return разом:</p><p>В коде выше foo будет удачно завершаться и с  'число подошло', и с 'Ошибка обработана'. В таком коде реджектов не будет. Но в отличие от одного из примеров выше, foo завершится со значением canRejectOrReturn, а не с undefined.</p><p>Вы можете убедиться в этом сами, убрав строку return await canRejectOrReturn():</p><h2>Популярные ошибки и подводные камни</h2><p>Из-за сложных манипуляций с промисами и async/await концепциями вы можете встретиться с различными тонкостями, что может привести к ошибкам.</p><h3>Не забывайте await</h3><p>Частая ошибка заключается в том, что перед промисом забывается ключевое слово await:</p><p>Обратите внимание, здесь не используется ни await, ни return. Функция foo всегда будет завершаться с undefined (без задержки в 1 секунду). Тем не менее, промис будет выполняться. Если промис будет выдавать ошибку либо реджект, то будет вызываться UnhandledPromiseRejectionWarning.</p><h3>async-функции в обратных вызовах</h3><p>async-функции часто используются в .map или .filter в качестве коллбэков. Вот пример — допустим, существует функция fetchPublicReposCount(username), которая возвращает количество открытых репозиториев на GitHub. Есть 3 пользователя, чьи показатели нужно взять. Используется такой код:</p><p>И для того, чтобы получить количество репозиториев пользователей (['ArfatSalman', 'octocat', 'norvig']), код должен выглядеть как-то так:</p><p>Обратите внимание на слово await в обратном вызове функции .map. Можно было бы ожидать, что переменная counts будет содержать число — количество репозиториев. Но как было сказано ранее, все async-функции возвращают промисы. Следовательно, counts будет массивом промисов. .map вызывает анонимной коллбэк для каждого пользователя.</p><h3>Слишком последовательное использование await</h3><p>Допустим, есть такой код:</p><p>В переменную count помещается количество репозиториев, потом это количество добавляется в массив counts. Проблема этого кода в том, что пока с сервера не придут данные первого пользователя, все последующие пользователи будут находиться в ожидании. Получается, что в один момент времени обрабатывается только один пользователь.</p><p>Если на обработку одного пользователя будет уходить 300 мс, то на всех пользователей уйдёт почти секунда. В этом случае затрачиваемое время будет линейно зависеть от количества пользователей. Поскольку получение количества репозиториев не зависит друг от друга, то можно распараллелить эти процессы. Тогда пользователи будут обрабатываться одновременно, а не последовательно. Для этого понадобятся .map и Promise.all.</p><p>Promise.all на входе получает массив промисов и возвращает промис. Возвращаемый промис завершается после окончания всех промисов в массиве либо при первом реджекте. Возможно, все эти промисы не запустятся строго одновременно. Чтобы добиться строгого параллелизма, взгляните на <a href="https://github.com/sindresorhus/p-map">p-map</a>. А если нужно, чтобы async-функции были более адаптивными, посмотрите на <a href="https://github.com/tc39/proposal-async-iteration">Async Iterators</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Асинхронность в программировании</title>
      <link>https://tproger.ru/articles/asynchronous-programming</link>
      <comments>https://tproger.ru/articles/asynchronous-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/asynchronous-programming</guid>
      <description><![CDATA[<p>Синхронные вызовы блокируют поток, пока идёт ввод-вывод, и в высоконагруженных системах программа в основном ждёт — здесь помогает асинхронность.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/asynchronous-programming">Асинхронность в программировании</a>»</p>]]></description>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Производительность]]></category>
      <category><![CDATA[Сервер]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 20 Dec 2018 15:04:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Традиционно в программировании используют синхронное программирование — последовательное выполнение инструкций с синхронными системными вызовами, которые полностью блокируют поток выполнения, пока системная операция, например чтение с диска, не завершится. В качестве примера ниже написан echo-сервер:</p><p>При вызове методов read() и write() текущий поток исполнения будет прерван в ожидании ввода-вывода по сети. Причём большую часть времени программа будет просто ждать. В высоконагруженных системах чаще всего так и происходит — почти всё время программа чего-то ждёт: диска, СУБД, сети, UI, в общем, какого-то внешнего, независимого от самой программы события. В малонагруженных системах это можно решить созданием нового потока для каждого блокирующего действия. Пока один поток спит, другой работает.</p><p>Но что делать, когда пользователей очень много? Если создавать на каждого хотя бы один поток, то производительность такого сервера резко упадёт из-за того, что контекст исполнения потока постоянно сменяется. Также на каждый поток создаётся свой контекст исполнения, включая память для стека, которая имеет минимальный размер в 4 КБ. Эту проблему может решить асинхронное программирование.</p><h2>Асинхронность</h2><p>Асинхронность в программировании — выполнение процесса в неблокирующем режиме системного вызова, что позволяет потоку программы продолжить обработку. Реализовать асинхронное программирование можно несколькими способами, о которых вы узнаете ниже.</p><h3>Callbacks</h3><p>Для написания асинхронной программы можно использовать callback-функции (от англ. callback — обратный вызов) — функции, которые будут вызваны асинхронно каким-либо обработчиком событий после завершения задачи. Переписанный пример сервера на callback-функциях:</p><p>В wait_connection() мы всё ещё ждём чего-то, но теперь вместе с этим внутри функции wait_connection() может быть реализовано подобие планировщика ОС, но с callback-функциями (пока мы ждём нового соединения, почему бы не обработать старые? Например, через очередь). Callback-функция вызывается, если в сокете появились новые данные — лямбда в async_read(), либо данные были записаны — лямбда в async_write().</p><p>В результате мы получили асинхронную работу нескольких соединений в одном единственном потоке, который намного реже будет ждать. Эту асинхронность можно также распараллелить, чтобы получить полный профит от утилизации процессорного времени.</p><p>У такого подхода есть несколько проблем. Первую в шутку называют callback hell. Достаточно погуглить картинки на эту тему, чтобы понять, насколько это нечитаемо и некрасиво. В нашем примере всего две вложенные callback-функции, но их может быть намного больше.</p><p>Вторая проблема заключается в том, что код перестал выглядеть как синхронный: появились «прыжки» из wait_connection() в лямбды, например лямбда, переданная в async_write(), что нарушает последовательность кода, из-за чего становится невозможно предсказать, в каком порядке будут вызваны лямбды. Это усложняет чтение и понимание кода.</p><h3>Async/Await</h3><p>Попробуем сделать асинхронный код так, чтобы он выглядел как синхронный. Для большего понимания немного поменяем задачу: теперь нам необходимо прочитать данные из СУБД и файла по ключу, переданному по сети, и отправить результат обратно по сети.</p><p>Пройдём по программе построчно:</p><ul><li>Ключевое слово async в заголовке функции говорит компилятору, что функция асинхронная и её нужно компилировать по-другому. Каким именно образом он будет это делать, написано ниже.</li><li>Первые три строки функции: создание и ожидание соединения.</li><li>Следующая строка делает асинхронное чтение, не прерывая основной поток исполнения.</li><li>Следующие две строки делают асинхронный запрос в базу данных и чтение файла. Оператор await приостанавливает текущую функцию, пока не завершится выполнение асинхронной задачи чтения из БД и файла.</li><li>В последних строках производится асинхронная запись в сокет, но лишь после того, как мы дождёмся асинхронного чтения из БД и файла.</li></ul><p>Это быстрее, чем последовательное ожидание сначала БД, затем файла. Во многих реализациях производительность async/await лучше, чем у классических callback-функций, при этом такой код читается как синхронный.</p><h3>Корутины</h3><p>Описанный выше механизм называется сопрограммой. Часто можно услышать вариант «корутина» (от англ. coroutine — сопрограмма).</p><p>Далее будут описаны различные виды и способы организации сопрограмм.</p><p>Несколько точек входа</p><p>По сути корутинами называются функции, имеющие несколько точек входа и выхода. У обычных функций есть только одна точка входа и несколько точек выхода. Если вернуться к примеру выше, то первой точкой входа будет сам вызов функции оператором asynс, затем функция прервёт своё выполнение вместо ожидания БД или файла. Все последующие await будут не запускать функцию заново, а продолжать её исполнение в точке предыдущего прерывания. Да, во многих языках в корутине может быть несколько await’ов.</p><p>Для большего понимания рассмотрим код на языке Python:</p><p>Программа выведет всю последовательность чисел факториала с номерами от 0 до 41.</p><p>Функция async_factorial() вернёт объект-генератор, который можно передать в функцию next(), а она продолжит выполнение корутины до следующего оператора yield с сохранением состояния всех локальных переменных функции. Функция next() возвращает то, что передаёт оператор yield внутри корутины. Таким образом, функция async_factorial() в теории имеет несколько точек входа и выхода.</p><p>Stackful и Stackless</p><p>В зависимости от использования стека корутины делятся на stackful, где каждая из корутин имеет свой стек, и stackless, где все локальные переменные функции сохраняются в специальном объекте.</p><p>Так как в корутинах мы можем в любом месте поставить оператор yield, нам необходимо где-то сохранять весь контекст функции, который включает в себя фрейм на стеке (локальные переменные) и прочую метаинформацию. Это можно сделать, например, полной подменой стека, как это делается в stackful корутинах.</p><p>На рисунке ниже вызов async создаёт новый стек-фрейм и переключает исполнение потока на него. Это практически новый поток, только исполняться он будет асинхронно с основным.</p><p>yield в свою очередь возвращает обратно предыдущий стек-фрейм на исполнение, сохраняя ссылку на конец текущего в предыдущий стек.</p><figure><img src="https://media.tproger.ru/uploads/2018/12/image1-3.png" alt="" /></figure><p>Наличие собственного стека позволяет делать yield из вложенных вызовов функций, но такие вызовы сопровождаются полным созданием/сменой контекста исполнения программы, что медленней, чем stackless корутины.</p><p>Более производительными, но вместе с тем и более ограниченными, являются stackless корутины. Они не используют стек, и компилятор преобразует функцию, содержащую корутины, в конечный автомат без корутин. Например, код:</p><p>Будет преобразован в следующий псевдокод:</p><p>По сути здесь создаётся класс, который сохраняет всё состояние функции, а также последнюю точку вызова yield. У такого подхода есть проблема: yield может быть вызван только в теле функции-корутины, но не из вложенных функций.</p><p>Симметричные и асимметричные</p><p>Корутины также делятся на симметричные и асимметричные.</p><p>Симметричные имеют глобальный планировщик корутин, который и выбирает среди всех ожидающих асинхронных операций ту, которую стоит выполнить следующей. Примером является планировщик, о котором говорилось в начале функции wait_connection().</p><p>В асимметричных корутинах нет глобального планировщика, и программист вместе с поддержкой компилятора сам выбирает, какую корутину и когда исполнять. Большинство реализаций корутин асимметричные.</p><h2>Вывод</h2><p>Асинхронное программирование является очень мощным инструментом для оптимизации высоконагруженных программ с частым ожиданием системы. Но, как и любую сложную технологию, её нельзя использовать только потому, что она есть. Необходимо всегда задавать себе вопрос: а нужна ли мне эта технология? Какую практическую пользу она мне даст? Иначе разработчики рискуют потратить очень много сил, времени и денег, не получив никакого профита.</p><p>Смотрите также: Асинхронное программирование в Python</p>]]></content:encoded>
    </item>
    <item>
      <title>Основные методы ввода-вывода (I/O)</title>
      <link>https://tproger.ru/translations/diversity-of-input-output-io</link>
      <comments>https://tproger.ru/translations/diversity-of-input-output-io?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сергей Бугаев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/diversity-of-input-output-io</guid>
      <description><![CDATA[<p>Основные методы ввода-вывода: опрос, аппаратные прерывания, асинхронный ввод-вывод. Описана классификация, примеры: файлы, сеть, дисплей, мышь.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/diversity-of-input-output-io">Основные методы ввода-вывода (I/O)</a>»</p>]]></description>
      <category><![CDATA[Для начинающих]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 12 Jun 2018 12:05:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>Начинающему разработчику зачастую сложно разобраться в средствах ввода-вывода (I/O), поскольку присутствует небольшая путаница в определении различий этих способов. В данном материале попробуем заполнить этот пробел и разобраться в классификации основных типов ввода-вывода.</p><h3>I/O в аппаратном обеспечении</h3><p>В современных операционных системах средства ввода-вывода представляют собой способы взаимодействия между обработчиком информации и внешним миром. Сюда можно отнести чтение или запись файлов на жёсткий диск или SSD, отправку и получение данных по сети, отображение информации на мониторе и получение ввода с мыши и клавиатуры.</p><p>Способы взаимодействия современных ОС с периферийными устройствами зависят от типа устройства, возможностей прошивки и аппаратного обеспечения. Можно считать, что периферия может обрабатывать несколько запросов одновременно. То есть время последовательного взаимодействия прошло. В этом смысле все взаимодействия устройств ввода-вывода с процессором асинхронны и находятся на стороне аппаратного обеспечения.</p><p>Такой асинхронный механизм называется аппаратным прерыванием. В простом случае, процессор отправляет запрос к внешнему устройству и начинает бесконечный цикл, в котором он каждый раз запрашивает у периферии, готовы ли данные, которые процессор мог бы считать, и ждёт в цикле, пока периферия не выдаст ему эти данные. Этот метод ввода-вывода называется методом опроса поскольку процессор должен постоянно проверять состояние периферии.</p><p>На самом деле, на современном оборудовании процессор просит периферию выполнить действие и забывает о нём, продолжая обрабатывать другие инструкции. Как только устройство завершит свою работу, оно сообщает об этом процессору, вызывая прерывание. Это происходит на уровне аппаратного обеспечения, и при этом процессору никогда не приходится ожидать или опрашивать периферию, что освобождает его для другой работы, пока периферия сама не сообщит о своей готовности.</p><h3>I/O в программном обеспечении</h3><p>В среде программного обеспечения существует большое количество видов ввода и вывода: блокирующий, неблокирующий, мультиплексированный и асинхронный. Давайте рассмотрим их по очереди.</p><h4>Блокирующий метод</h4><p>Помните, что любая пользовательская программа запускается внутри процесса, а код выполняется в контексте потока? Предположим, вы пишете программу, которой нужно читать информацию из файла. С блокирующим вводом-выводом вы просите ОС «усыпить» ваш поток и «разбудить» его только после того, как данные из файла будут доступны для чтения.</p><p>То есть блокирующий ввод-вывод называется так, потому что поток, который его использует, блокируется и переходит в режим ожидания, пока ввод-вывод не будет завершён.</p><h4>Неблокирующий метод</h4><p>Проблема метода блокировки заключается в том, что поток будет спать, пока ввод-вывод не завершится. Поток не сможет выполнять никаких других задач, кроме ожидания завершения ввода-вывода. Иногда вашей программе больше и не надо ничего делать. В противном случае во время ожидания ввода-вывода было бы полезно выполнять другие задачи.</p><p>Один из способов осуществить это — использовать неблокирующий ввод-вывод. Его идея заключается в том, что когда программа делает вызов на чтение файла, ОС не будет блокировать поток, а просто вернёт ей либо готовые данные, либо информацию о том, что ввод-вывод ещё не закончен. Это не заблокирует поток, но программе придётся позже проверять, завершён ли ввод-вывод. Это означает, что ПО может по-разному реагировать в зависимости от того, завершён ли ввод-вывод и выполнять другие задачи. Когда же программе снова понадобится ввод-вывод, она сможет повторно попробовать прочесть содержимое файла, и если ввод-вывод завершён, то получит содержимое файла. В противном случае ПО снова получит сообщение о том, что операция ещё не завершена и сможет заняться другими задачами.</p><h4>Мультеплексированный метод</h4><p>Проблема с неблокирующим вводом-выводом в том, что с ним не удобно работать, если задачи, которые выполняет программа, ожидая ввода-вывода, сами из себя представляют другой ввод-вывод.</p><p>Хорошо, если ПО просит ОС прочитать содержимое из файла А, после чего выполняет какие-нибудь сложные вычисления. Затем проверяет, завершилось ли чтение файла А, и если да, то просто продолжает ту работу, для которой нужно было содержимое файла, а иначе снова выполняет некоторое количество сложных вычислений и так далее.</p><p>Но что, если программе не нужно выполнять сложные вычисления? Ей просто нужно прочесть файл A и одновременно файл B. Пока ПО ожидает завершения обработки файла А, оно делает неблокирующий вызов чтения содержимого файла В. Во время ожидания обработки программе больше нечего делать, потому она входит в бесконечный цикл опроса, проверяя, готово ли A и готово ли B, снова и снова. Это либо нагрузит процессор проверками состояния ваших неблокирующих вызовов, либо вам придётся вручную добавить какое-то произвольное время, которое ваш поток будет «спать», а значит, программа немного позже заметит, что ввод-вывод готов, что отрицательно скажется на пропускной способности ПО.</p><p>Во избежание этого можно использовать мультиплексированный ввод-вывод. Он тоже блокирует поток на операциях ввода-вывода, но вместо того, чтобы производить блокировку по очереди, вы можете запланировать все операции ввода-вывода, которые вам нужно сделать, и блокировать их все. Операционная система разбудит поток, когда какая-нибудь из операций завершится. В некоторых реализациях мультиплексированного ввода-вывода можно даже точно указать, что вы хотите, чтобы поток разбудили, только когда заданный набор операций ввода-вывода будет завершён, например, когда файлы A и C, или файлы B и D будут готовы.</p><p>Таким образом ПО делает неблокирующий вызов чтения файла A, потом неблокирующий вызов чтения файла B, и наконец говорит ОС: усыпи мой поток, и разбуди его, когда A и B будут оба готовы, или когда один из них будет готов.</p><h4>Асинхронный метод</h4><p>Проблема мультиплексированного ввода-вывода в том, что поток всё-таки спит, пока ввод-вывод не будет готов для обработки. Для многих программ это подходит, поскольку у них нет других задач, пока они ждут завершения операций ввода-вывода. Но иногда у них есть и другие задачи.</p><p>Например, ПО вычисляет цифры числа π и одновременно суммирует значения из нескольких файлов. Вам хотелось бы запланировать все операции чтения файлов, пока программа ждёт их выполнения, вычислять цифры числа π. Когда какой-нибудь из файлов будет прочитан, ПО прибавит записанное в нём значение и продолжит вычислять цифры числа π дальше, пока ещё один файл не будет прочитан.</p><p>Чтобы это работало, нужно, чтобы ваше вычисление цифр числа π могло быть прервано вводом-выводом, когда он завершается. Это можно сделать с помощью обратных вызовов, связанных с событиями. Вызов на чтение принимает функцию обратного вызова и возвращается немедленно. Когда ввод-вывод завершается, операционная система остановит ваш поток и выполнит обратный вызов. Когда обратный вызов завершится, система возобновит работу вашего потока.</p><p>Чтение по теме: <a href="https://tproger.ru/translations/asynchronous-programming-in-python/">Асинхронное программирование в Python</a></p><h3>Многопоточность или однопоточность?</h3><p>Вы, наверно, заметили, что все вышеописанные способы ввода-вывода работают в рамках одного потока, главного потока вашего приложения. На самом деле, для выполнения ввода-вывода не требуется отдельный поток, поскольку, как вы видели в начале статьи, вся периферия выполняет ввод-вывод асинхронно. Поэтому и возможно делать блокирующий, неблокирующий, мультиплексированный и асинхронный ввод-вывод в однопоточной модели и одновременный ввод-вывод может работать без поддержки многопоточности.</p><p>Но обработка результатов операций ввода-вывода может быть и многопоточной. Это позволяет программе делать одновременные вычисления поверх одновременного ввода-вывода. Так что, ничто не мешает вам совмещать многопоточность и эти механизмы ввода-вывода.</p><p>В самом деле, есть пятый популярный метод ввода-вывода, который требует многопоточности. Его часто путают с неблокирующим или асинхронным, поскольку он похож по интерфейсу на те два. Он работает просто: он использует блокирующий ввод-вывод, но каждый блокирующий вызов делается в отдельном потоке. В зависимости от реализации, вызов либо принимает функцию обратного вызова, либо использует модель опроса, как если он возвращает Future.</p>]]></content:encoded>
    </item>
    <item>
      <title>Асинхронное программирование в Python</title>
      <link>https://tproger.ru/translations/asynchronous-programming-in-python</link>
      <comments>https://tproger.ru/translations/asynchronous-programming-in-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Дмитрий Юрченко]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/asynchronous-programming-in-python</guid>
      <description><![CDATA[<p>Основы асинхронности в Python: чем Asyncio отличается от потоков, какие проблемы создаёт многопоточность и зачем нужно переключение контекста.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/asynchronous-programming-in-python">Асинхронное программирование в Python</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 13 Jun 2017 13:28:22 GMT</pubDate>
      <content:encoded><![CDATA[<p>Асинхронное программирование на Python становится все более популярным. Для этих целей существует множество различных библиотек. Самая популярная из них – <a href="https://docs.python.org/3/library/asyncio.html">Asyncio</a>, которая является стандартной библиотекой Python 3.4. Из этой статьи вы узнаете, что такое асинхронное программирование и чем отличаются различные библиотеки, реализующие асинхронность в Python.</p><h2>По очереди</h2><p>В каждой программе строки кода выполняются поочередно. Например, если у вас есть строка кода, которая запрашивает что-либо с сервера, то это означает, что ваша программа не делает ничего во время ожидания ответа. В некоторых случаях это допустимо, но во многих — нет. Одним из решений этой проблемы являются потоки (threads).</p><p>Потоки дают возможность вашей программе выполнять ряд задач одновременно. Конечно, у потоков есть ряд недостатков. Многопоточные программы являются более сложными и, как правило, более подвержены ошибкам. Они включают в себя такие проблемы: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8">состояние гонки</a> (race condition), взаимная (deadlock) и активная (livelock) <a href="https://ru.wikipedia.org/wiki/Взаимная_блокировка">блокировка,</a> <a href="https://en.wikipedia.org/wiki/Starvation_(computer_science)">исчерпание ресурсов</a> (resource starvation).</p><h2>Переключение контекста</h2><p>Хотя асинхронное программирование и позволяет обойти проблемные места потоков, оно было разработано для совершенно другой цели — для переключения контекста процессора. Когда у вас есть несколько потоков, каждое ядро процессора может запускать только один поток за раз. Для того, чтобы все потоки/процессы могли совместно использовать ресурсы, процессор очень часто переключает контекст. Чтобы упростить работу, процессор с произвольной периодичностью сохраняет всю контекстную информацию потока и переключается на другой поток.</p><p>Асинхронное программирование — это потоковая обработка программного обеспечения / пользовательского пространства, где приложение, а не процессор, управляет потоками и переключением контекста. В асинхронном программировании контекст переключается только в заданных точках переключения, а не с периодичностью, определенной CPU.</p><h2>Эффективный секретарь</h2><p>Теперь давайте рассмотрим эти понятия на примерах из жизни. Представьте секретаря, который настолько эффективен, что не тратит время впустую. У него есть пять заданий, которые он выполняет одновременно: отвечает на телефонные звонки, принимает посетителей, пытается забронировать билеты на самолет, контролирует графики встреч и заполняет документы. Теперь представьте, что такие задачи, как контроль графиков встреч, прием телефонных звонков и посетителей, повторяются не часто и распределены во времени. Таким образом, большую часть времени секретарь разговаривает по телефону с авиакомпанией, заполняя при этом документы. Это легко представить. Когда поступит телефонный звонок, он поставит разговор с авиакомпанией на паузу, ответит на звонок, а затем вернется к разговору с авиакомпанией. В любое время, когда новая задача потребует внимания секретаря, заполнение документов будет отложено, поскольку оно не критично. Секретарь, выполняющий несколько задач одновременно, переключает контекст в нужное ему время. Он асинхронный.</p><p>Потоки — это пять секретарей, у каждого из которых по одной задаче, но только одному из них разрешено работать в определенный момент времени. Для того, чтобы секретари работали в потоковом режиме, необходимо устройство, которое контролирует их работу, но ничего не понимает в самих задачах. Поскольку устройство не понимает характер задач, оно постоянно переключалось бы между пятью секретарями, даже если трое из них сидят, ничего не делая. Около 57% (чуть меньше, чем 3/5) переключения контекста были бы напрасны. Несмотря на то, что переключение контекста процессора является невероятно быстрым, оно все равно отнимает время и ресурсы процессора.</p><h2>Зеленые потоки</h2><p>Зеленые потоки (green threads) являются примитивным уровнем асинхронного программирования. Зеленый поток — это обычный поток, за исключением того, что переключения между потоками производятся в коде приложения, а не в процессоре. <a href="http://www.gevent.org">Gevent</a> — известная Python-библиотека для использования зеленых потоков. Gevent — это зеленые потоки и сетевая библиотека неблокирующего ввода-вывода Eventlet. Gevent.monkey изменяет поведение стандартных библиотек Python таким образом, что они позволяют выполнять неблокирующие операции ввода-вывода. Вот пример использования Gevent для одновременного обращения к нескольким URL-адресам:</p><p>Как видите, API-интерфейс Gevent выглядит так же, как и потоки. Однако за кадром он использует сопрограммы (coroutines), а не потоки, и запускает их в цикле событий (event loop) для постановки в очередь. Это значит, что вы получаете преимущества потоков, без понимания сопрограмм, но вы не избавляетесь от проблем, связанных с потоками. Gevent — хорошая библиотека, но только для тех, кто понимает, как работают потоки.</p><p>Давайте рассмотрим некоторые аспекты асинхронного программирования. Один из таких аспектов — это цикл событий. Цикл событий — это очередь событий/заданий и цикл, который вытягивает задания из очереди и запускает их. Эти задания называются сопрограммами. Они представляют собой небольшой набор команд, содержащих, помимо прочего, инструкции о том, какие события при необходимости нужно возвращать в очередь.</p><h2>Функция обратного вызова (callback)</h2><p>В Python много библиотек для асинхронного программирования, наиболее популярными являются Tornado, Asyncio и Gevent. Давайте посмотрим, как работает <a href="http://www.tornadoweb.org/">Tornado</a>. Он использует стиль обратного вызова (callbacks) для асинхронного сетевого ввода-вывода. Обратный вызов — это функция, которая означает: «Как только это будет сделано, выполните эту функцию». Другими словами, вы звоните в службу поддержки и оставляете свой номер, чтобы они, когда будут доступны, перезвонили, вместо того, чтобы ждать их ответа.<br />Давайте посмотрим, как сделать то же самое, что и выше, используя Tornado:</p><p>Предпоследняя строка кода вызывает метод AsyncHTTPClient.fetch, который получает данные по URL-адресу неблокирующим способом. Этот метод выполняется и возвращается немедленно. Поскольку каждая следующая строка будет выполнена до того, как будет получен ответ по URL-адресу, невозможно получить объект, как результат выполнения метода. Решение этой проблемы заключается в том, что метод fetch вместо того, чтобы возвращать объект, вызывает функцию с результатом или обратный вызов. Обратный вызов в этом примере — handle_response.</p><p>В примере вы можете заметить, что первая строка функции handle_response проверяет наличие ошибки. Это необходимо, потому что невозможно обработать исключение. Если исключение было создано, то оно не будет отрабатываться в коде из-за цикла событий. Когда fetch выполняется, он запускает HTTP-запрос, а затем обрабатывает ответ в цикле событий. К тому моменту, когда возникнет ошибка, стек вызовов будет содержать только цикл событий и текущую функцию, при этом нигде в коде не сработает исключение. Таким образом, любые исключения, созданные в функции обратного вызова, прерывают цикл событий и останавливают выполнение программы. Поэтому все ошибки должны быть переданы как объекты, а не обработаны в виде исключений. Это означает, что если вы не проверили наличие ошибок, то они не будут обрабатываться.<br />Другая проблема с обратными вызовами заключается в том, что в асинхронном программировании единственный способ избегать блокировок — это обратный вызов. Это может привести к очень длинной цепочке: обратный вызов после обратного вызова после обратного вызова. Поскольку теряется доступ к стеку и переменным, вы в конечном итоге переносите большие объекты во все ваши обратные вызовы, но если вы используете сторонние API-интерфейсы, то не можете передать что-либо в обратный вызов, если он этого не может принять. Это также становится проблемой, потому что каждый обратный вызов действует как поток. Например, вы хотели бы вызвать три API-интерфейса и дождаться, пока все три вернут результат, чтобы его обобщить. В Gevent вы можете это сделать, но не с обратными вызовами. Вам придется немного поколдовать, сохраняя результат в глобальной переменной и проверяя в обратном вызове, является ли результат окончательным.</p><h2>Сравнения</h2><p>Если вы хотите предотвратить блокировку ввода-вывода, вы должны использовать либо потоки, либо асинхронность. В Python вы выбираете между зелеными потоками и асинхронным обратным вызовом. Вот некоторые из их особенностей:</p><h3>Зеленые потоки</h3><ul><li>потоки управляются на уровне приложений, а не аппаратно;</li><li>включают в себя все проблемы потокового программирования.</li></ul><h3>Обратный вызов</h3><ul><li>сопрограммы невидимы для программиста;</li><li>обратные вызовы ограничивают использование исключений;</li><li>обратные вызовы трудно отлаживаются.</li></ul><h2>Как решить эти проблемы?</h2><p>Вплоть до Python 3.3 зеленые потоки и обратный вызов были оптимальными решениями. Чтобы превзойти эти решения, нужна поддержка на уровне языка. Python должен каким-то образом частично выполнить метод, прекратить выполнение, поддерживая при этом объекты стека и исключения. Если вы знакомы с концепциями Python, то понимаете, что я намекаю на генераторы. Генераторы позволяют функции возвращать список по одному элементу за раз, останавливая выполнение до того момента, когда следующий элемент будет запрошен. Проблема с генераторами заключается в том, что они полностью зависят от функции, вызывающей его. Другими словами, генератор не может вызвать генератор. По крайней мере так было до тех пор, пока в <a href="https://docs.python.org/3/whatsnew/3.3.html#pep-380">PEP 380</a> не добавили синтаксис yield from, который позволяет генератору получить результат другого генератора. Хоть асинхронность и не является главным назначением генераторов, они содержат весь функционал, чтобы быть достаточно полезными. Генераторы поддерживают стек и могут создавать исключения. Если бы вы написали цикл событий, в котором бы запускались генераторы, у вас получилась бы отличная асинхронная библиотека. Именно так и была создана библиотека <a href="https://docs.python.org/3/library/asyncio.html">Asyncio</a>.</p><p>Все, что вам нужно сделать, это добавить декоратор @coroutine, а Asyncio добавит генератор в сопрограмму. Вот пример того, как обработать те же три URL-адреса, что и раньше:</p><p>Прим. перев.  В примерах используется aiohttp версии 1.3.5. В последней версии библиотеки <a href="http://aiohttp.readthedocs.io/en/stable/client.html#make-a-request">синтаксис другой</a>.</p><p>Несколько особенностей, которые нужно отметить:</p><ul><li>ошибки корректно передаются в стек;</li><li>можно вернуть объект, если необходимо;</li><li>можно запустить все сопрограммы;</li><li>нет обратных вызовов;</li><li>строка 10 не выполнится до тех пор, пока строка 9 не будет полностью выполнена.</li></ul><p>Единственная проблема заключается в том, что объект выглядит как генератор, и это может вызвать проблемы, если на самом деле это был генератор.</p><h2>Async и Await</h2><p>Библиотека Asyncio довольно мощная, поэтому Python решил сделать ее стандартной библиотекой. В синтаксис также добавили ключевое слово async. Ключевые слова предназначены для более четкого обозначения асинхронного кода. Поэтому теперь методы не путаются с генераторами. Ключевое слово async идет до def, чтобы показать, что метод является асинхронным. Ключевое слово await показывает, что вы ожидаете завершения сопрограммы. Вот тот же пример, но с ключевыми словами async/await:</p><p>Программа состоит из метода async. Во время выполнения он возвращает сопрограмму, которая затем находится в ожидании.</p><h2>Заключение</h2><p>В Python встроена отличная асинхронная библиотека. Давайте еще раз вспомним проблемы потоков и посмотрим, решены ли они теперь:</p><ul><li>процессорное переключение контекста: Asyncio является асинхронным и использует цикл событий. Он позволяет переключать контекст программно;</li><li>состояние гонки: поскольку Asyncio запускает только одну сопрограмму и переключается только в точках, которые вы определяете, ваш код не подвержен проблеме гонки потоков;</li><li>взаимная/активная блокировка: поскольку теперь нет гонки потоков, то не нужно беспокоиться о блокировках. Хотя взаимная блокировка все еще может возникнуть в ситуации, когда две сопрограммы вызывают друг друга, это настолько маловероятно, что вам придется постараться, чтобы такое случилось;</li><li>исчерпание ресурсов: поскольку сопрограммы запускаются в одном потоке и не требуют дополнительной памяти, становится намного сложнее исчерпать ресурсы. Однако в Asyncio есть пул «исполнителей» (executors), который по сути является пулом потоков. Если запускать слишком много процессов в пуле исполнителей, вы все равно можете столкнуться с нехваткой ресурсов.</li></ul><p>Несмотря на то, что Asyncio довольно хорош, у него есть и проблемы. Во-первых, Asyncio был добавлен в Python недавно. Есть некоторые недоработки, которые еще не исправлены. Во-вторых, когда вы используете асинхронность, это значит, что весь ваш код должен быть асинхронным. Это связано с тем, что выполнение асинхронных функций может занимать слишком много времени, тем самым блокируя цикл событий.</p><p>Существует несколько вариантов асинхронного программирования в Python. Вы можете использовать зеленые потоки, обратные вызовы или сопрограммы. Хотя вариантов много, лучший из них — Asyncio. Если используете Python 3.5, то вам лучше использовать эту библиотеку, так как она встроена в ядро ​​python.</p>]]></content:encoded>
    </item>
    <item>
      <title>Асинхронный веб, или Что такое веб-сокеты</title>
      <link>https://tproger.ru/translations/what-are-web-sockets</link>
      <comments>https://tproger.ru/translations/what-are-web-sockets?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Vladimir Gabrinevski]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/what-are-web-sockets</guid>
      <description><![CDATA[<p>Веб-сокеты поддерживают двунаправленный обмен данными между браузером и сервером без повторных запросов; в отличие от HTTP соединение остаётся интерактивным.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/what-are-web-sockets">Асинхронный веб, или Что такое веб-сокеты</a>»</p>]]></description>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Для продолжающих]]></category>
      <category><![CDATA[Асинхронное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 31 May 2017 09:15:25 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://ru.wikipedia.org/wiki/WebSocket">Веб-сокеты</a> (Web Sockets) — это передовая технология, которая позволяет создавать интерактивное соединение между клиентом (браузером) и сервером для обмена сообщениями в режиме реального времени. Веб-сокеты, в отличие от HTTP, позволяют работать с двунаправленным потоком данных, что делает эту технологию совершенно уникальной. Давайте разберемся, как работает эта технология и чем она отличается от HTTP.</p><h2>Как работает HTTP?</h2><figure><img src="https://media.tproger.ru/uploads/2017/05/OeZDmUNOMec.jpg" alt="" /></figure><p>Вы наверняка знаете, что такое HTTP (или HTTPS), поскольку встречаетесь с этим протоколом каждый день в своём браузере. Браузер постоянно спрашивает у сервера, есть ли для него новые сообщения, и получает их.</p><p>Вы также можете знать, что HTTP позволяет использовать разные типы запросов, такие как POST, GET или PUT, каждый из которых имеет своё назначение.</p><h2>Как работают веб-сокеты?</h2><figure><img src="https://media.tproger.ru/uploads/2017/05/nejJ2jV8c3o.jpg" alt="" /></figure><p>Веб-сокетам же для ответа не нужны ваши повторяющиеся запросы. Достаточно выполнить один запрос и ждать отклика. Вы можете просто слушать сервер, который будет отправлять вам сообщения по мере готовности.</p><p>Веб-сокеты можно использовать, если вы разрабатываете:</p><ul><li>приложения реального времени;</li><li>чат-приложения;</li><li>IoT-приложения;</li><li>многопользовательские игры.</li></ul><h2>Когда следует избегать использования веб-сокетов?</h2><p>Практически никогда. Единственный минус — это несовместимость с некоторыми браузерами, но уже 95 % браузеров <a href="http://caniuse.com/#feat=websockets">поддерживают</a> веб-сокеты.</p><p>В некоторых случаях веб-сокеты вам всё же не понадобятся. Если вы создаёте простую CMS, вам вряд ли пригодится функциональность в режиме реального времени. Также не стоит использовать веб-сокеты в REST API, поскольку вам хватит таких HTTP-запросов, как GET, POST, DELETE и PUT.</p><h2>Практические примеры</h2><p>В примерах ниже для клиента используется JavaScript, а для сервера — Node.js. Примеры очень просты и вряд ли пригодятся на практике, но зато позволят разобраться в сути.</p><h3>Веб-сокеты</h3><p>Клиент:</p><p>Сервер:</p><p>Вот иллюстрация работы веб-сокетов:</p><figure><img src="https://media.tproger.ru/uploads/2017/05/1-GnRBuOtkxICDBJOb709HA.gif" alt="" /></figure><h3>Эквивалент в HTTP</h3><p>Так как HTTP должен постоянно проверять канал на наличие новых сообщений, можно использовать «грязную» проверку (dirty check) — подход, при котором клиент с заданной периодичностью (допустим, каждые 200 мс) проверяет наличие новых сообщений на сервере.</p><p>Чтобы не вникать в <a href="https://ru.wikipedia.org/wiki/XMLHttpRequest">XMLHttpRequest</a>, можно использовать библиотеку <a href="https://github.com/mzabriskie/axios">Axios</a>. Она декларативна и очень понятна.</p><p>Клиент:</p><p>Сервер:</p><h2>Заключение</h2><p>Веб-сокеты являются одной из самых перспективных веб-технологий, которую уже сейчас используют многие разработчики. Она отлично подходит для взаимодействия в режиме реального времени, в том числе в онлайн-играх.</p>]]></content:encoded>
    </item>
  </channel>
</rss>