Асинхронное программирование

Асинхронное программирование — концепция программирования, при которой результат выполнения функции доступен спустя некоторое время в виде асинхронного (нарушающего стандартный порядок выполнения) вызова. Запуск длительных операций происходит без ожидания их завершения и не блокирует дальнейшее выполнение программы.

Отличие синхронного кода от асинхронного

Когда применяется асинхронность

Концепция асинхронного программирования стала популярна благодаря масштабным приложениям, экосистемам, состоящим из множества сервисов, — супераппов. Она используется для улучшения пользовательского опыта. Например, когда мобильное приложение запрашивает и загружает данные с сервера, экран смартфона демонстрирует активность программы — анимированную заставку или раздел помощи. Основной поток приложения при этом находится в режиме ожидания данных, загружаемых с удаленного сервера. 

Использование принципов асинхронного программирования повышает производительность, отзывчивость и стабильность приложений, которые отвечают следующим характеристикам:

  1. Непрерывно выполняют большое количество заданий — среди множества операций всегда будет та, которая должна работать, когда остальные блокируются.
  2. Задания выполняют множество операций по вводу и выводу информации, заставляя синхронную программу впустую тратить время на блокировки.
  3. Задания в основном независимы друг от друга, и необходимость обмена данными между операциями практически отсутствует.

Перечисленные условия отличают рабочий процесс серверного приложения, отвечающего на запросы клиента. Поэтому в последние годы популярны серверные платформы, способные работать в асинхронном режиме, такие как Node.js.

Что такое многопоточность

Многопоточность — это параллельное выполнение нескольких блоков (потоков) приложения независимо друг от друга. Например, платформа Node.js использует два вида потоков.

Основной. Обрабатывается циклом событий.

Вспомогательные. Обрабатываются в пуле воркеров — модуле для произведения операций ввода-вывода, которые особенно интенсивно нагружают процессор. Такие операции обычно возникают при обработке сетевых запросов и взаимодействии с системным диском.

Отличие асинхронности от многопоточности

Блокировка кода в синхронных приложениях

Синхронные приложения выполняют код последовательно и не возвращают контроль окружению до тех пор, пока вся программа не будет выполнена. На практике это часто приводит к зависаниям. Почти любой пользователь сталкивался с сообщением о том, что тот или иной скрипт замедляет загрузку веб-страницы.

У проблемы есть два решения — остановить загрузку скрипта или ждать его завершения. В любом случае посетитель может покинуть сайт. В худшем случае скрипт замедлит браузер настолько, что операционная система не сможет отреагировать на действия пользователя, пока веб-приложение не завершится и не разблокирует браузер.

Блокировка интерфейса во время выполнения ресурсоемкой операции

Проиллюстрируем блокировку на примере исполнения кода:

const btn = document.querySelector(‘button’);
btn.addEventListener(‘click’, () => {
let myDate;
for(let i = 0; i < 5000000; i++) {
let date = new Date();
myDate = date
}
console.log(myDate);
let pElem = document.createElement(‘p’);
pElem.textContent = ‘Это новый параграф.’;
document.body.appendChild(pElem);
});

При нажатии на кнопку запускается операция по расчету и выводу на консоль 5 000 000 дат. Новый параграф на странице появится только после завершения операции. Если же вместо расчета дат скрипт будет выполнять более сложные операции, браузер, скорее всего, на несколько минут зависнет.

Это происходит потому, что JavaScript — однопоточный язык. Он выполняет все задачи (потоки) последовательно. Некоторые программные платформы, например языки семейства .NET или Go, создавались с расчетом на асинхронное программирование и многопоточную обработку данных. Однако JavaScript разрабатывался для выполнения простых действий вроде обработки нажатий кнопок на веб-странице. Со временем разработчики создали несколько способов реализовать асинхронную обработку данных в JavaScript-приложениях.

Реализация асинхронности в JavaScript

Для добавления асинхронной обработки данных в приложения на JavaScript используются три подхода: коллбэки (функции обратного вызова), обещания (Promise) и Async/Await. Рассмотрим преимущества и недостатки каждого из этих решений.

Коллбэки

Коллбэк — функция, которая предназначена для отложенного выполнения после завершения работы другой функции. Переводится как «Перезвоните». Принцип работы напоминает заказ обратного телефонного звонка. Например, пользователь звонит в техподдержку, но срабатывает автоответчик с просьбой оставаться на линии, пока не освободится оператор, либо предлагает заказать обратный звонок. Когда оператор будет свободен, он перезвонит и поможет решить вопрос.

Таки образом в качестве параметра нужно передать функцию, которая должна быть вызвана позже (когда завершится асинхронный код).

Преимущества:

  1. Простота. Достаточно передать функцию, которую нужно запустить позже.
  2. Универсальность. Функции обратного вызова исполняются на любых платформах разными браузерами.

Недостатки:

  1. Появление многоуровневых вложений в код. Это называется callback hell («ад обратных вызовов»).
  2. Сложности с обработкой ошибок. Необходимо передавать ошибку по кругу, а не использовать традиционные Try/Catch.
  3. Неудобочитаемый код. Чтобы понять логику выполнения потока, нужно переходить с одного участка кода на другой.

Обещания (промисы, Promises)

Это объекты, которые позволяют упорядочить выполнение асинхронных вызовов. С помощью обещаний можно:

  • откладывать выполнение действия до того момента, когда завершится выполнение предыдущей операции;
  • реагировать на неудачное выполнение действия.

Сегодня это наиболее популярный подход для JavaScript.

Преимущества:

  1. Обещания можно объединять в цепочки для обработки сложных асинхронных потоков, не прибегая к глубокой вложенности, необходимой для обратных вызовов.
  2. Гибкость при составлении сложных асинхронных операций. Можно использовать обещание, которое разрешится первым, с помощью promise.race и выполнять несколько асинхронных операций одновременно с помощью promise.all.

Недостатки:

  1. «Проглоченные» исключения. Для обработки ошибок вместо традиционных Try/Catch необходимо объявить .catch. Без .catch некоторые браузеры молча «проглатывают» необработанные исключения в обещаниях.
«Проглоченные» исключения

  1. Появление ошибок при использовании API. Разработчик должен быть уверен, что код вернет обещание. В противном случае можно получить ошибку вроде «Cannot read property ‘then’ of undefined» («Невозможно прочитать свойство ‘then’ неопределенного значения»).
  2. Требуется решение для совместимости с устаревшими и неподдерживаемыми браузерами, например с Internet Explorer.

Async/Await

Концепция была представлена в стандарте ES2017 (речь о JavaScript). В основе метода — обещания, рассмотренные выше, поэтому Async/Await иногда называют синтаксическим сахаром. Концепция дает разработчику возможность писать более простой и понятный код для работы с обещаниями.

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

Функция приостанавливается на ключевом слове Await, пока не завершится вызов Async. Async-функции возвращают обещание, поэтому обещания по-прежнему используются для обработки ответа. Возвращаемое значение асинхронной функции автоматически обертывается в promise.resolve.

Последние версии Node имеют встроенную поддержку Async/Await. Современные браузеры тоже поддерживают эту функцию, но для поддержки Internet Explorer понадобится транспиляция (преобразование кода) с помощью Babel или TypeScript.

Преимущества:

  1. В отличие от обещаний, Async/Await позволяет использовать Try/Catch для обработки ошибок.
  2. Готовый код легче читать. Вложенность уменьшается благодаря ключевому слову Await. При использовании обратных вызовов и обещаний множество операций Async может привести к тому, что код будет сложнее читать. С Async/Await разработчик просто добавляет еще одно ключевое слово Await. Дополнительных вложений не требуется.
  3. Удобство отладки. При объединении нескольких операций Async в цепочку Async/Await создает более наглядную трассировку стека, поскольку отображает именно тот оператор Await, который завершился неудачей. При использовании цепочки обещаний сообщение об ошибке часто бывает неоднозначным. Кроме того, легко установить точку останова на операторе Async. В то же время в некоторых структурах обещаний разработчику придется провести рефакторинг, чтобы иметь возможность установить точку останова.

Недостатки:

  1. Снижение производительности. При использовании Async/Await скорость исполнения кода несколько снижается. Например, каждое ключевое слово Await приостанавливает исполнение, поэтому нет возможности выполнять несколько операций Async одновременно. Обещания, напротив, позволяют выполнять несколько операций Async одновременно, используя promise.all.
  2. Увеличение объема кода при транспиляции. Если возникает необходимость транспилировать код для устаревшей платформы, то в результате он получится большим. Например, при использовании Babel 8 строк превращаются в 37. Увеличение объема происходит один раз: если Async/Await применяется несколько раз, код в _asyncToGenerator используется повторно.

Другие подходы

Также для добавления асинхронной функциональности в JavaScript-приложения могут использоваться итераторы и генераторы, библиотека RxJS, которая дает возможность обрабатывать асинхронные вызовы с помощью множества событий.

RxJS устраняет ограничения, которые накладывает применение обещания. Например, оно может обрабатывать только одно событие и его нельзя отменить, что может привести к блокировке потока и лишнему использованию системных ресурсов. Библиотека предоставляет несколько каналов для упрощения и повышения эффективности обработки многошаговых событий.

Асинхронность и многопоточность — обязательные характеристики большинства современных приложений, поэтому квалифицированный веб-разработчик должен хорошо знать эти концепции. 

Освойте новую профессию

(рейтинг: 5, голосов: 4)
Добавить комментарий