2.2.2.5 Синхронные и асинхронные вызовы функций задач
Чтобы из одной задачи вызвать функцию другой задачи, необходимо, чтобы эта сторонняя задача приняла согласие на ее исполнение (то есть перешла в режим ожидания вызова данной функции). Функции задачи могут быть вызваны синхронно и асинхронно. При синхронном вызове задача-инициатор будет ждать окончания работы функции и только после этого продолжит свою работу. Асинхронный вызов предполагает передачу сообщения в очередь принимающей задачи, сам вызов фактически будет осуществлен тогда, когда это решит принимающая задача. Таким образом, вызов функции будет отложенным, то есть асинхронным по отношению к инициатору, так как инициатор после передачи сообщения продолжает исполнять свой процесс и не будет автоматически оповещен о том, когда данная функция реально исполнится в принимающей задаче.
2.2.2.6 Синхронный вызов функции пакета
Вызов функции пакета может осуществляться только синхронно, поскольку с пакетом не связана никакая очередь сообщений, которая необходима для вызова асинхронных функций.
2.2.2.7 События и подписки
С помощью механизма подписок на события одна задача может оповестить о событии множество других задач. Для этого оповещаемые задачи необходимо подписать на событие, то есть связать функции этих задач с событием, при возникновении которого и должны запускаться подписанные функции. Подписываемые на событие функции должны быть публичными и быть согласованными с сигнатурой события: список их параметров должен соответствовать списку параметров события. У событий, в отличие от функций, отсутствует понятие возвращаемого значения.
Подписка, изображаемая на диаграмме задач, является начальной (инициализационной): ее можно изменять в процессе выполнения программы. Другими словами, подписка в языке является динамической (аналогично этому, в дизайнере форм программ для ЭВМ, например, средствами интегрированной среды разработки Microsoft Visual Studio [34] или Borland Delphi [35], пользователь рисует только первоначальную конфигурацию компонентов, но может ее менять в коде, также может в процессе выполнения подменить обработчики событий). Выполнить подписку (как и отписку) можно из любого места программного кода.
При выполнении конкретной подписки, пользователь выбирает тип вызова функции. При выборе синхронного типа, вызывающая событие задача будет ждать, пока не исполнятся все подписанные функции. Поэтому использование синхронных подписок не рекомендуется, поскольку может привести к длительной задержке в работе задач.
2.2.2.8 Текстовый язык
Разработанный текстовый язык основан на концепциях языка С и Ада. Выбор именно такой базы для предлагаемого языка обуславливается следующими причинами:
1) большинство существующих разработчиков программ для микроконтроллеров создают свои программы на языке С, переход на принципиально новые нотации (например, язык Ада) усложняет выход продукта на рынок;
2) в нотацию языка С не заложено никаких средств для организации параллельного программирования.
Поэтому нотация языка является гибридом нотации языка С и Ada. Из языка Ада позаимствована нотация пакетов и задач. Таким образом, программный код представляет собой набор пакетов, функция одного из которых является точкой входа в программу. Пакеты содержат переменные, функции, задачи, события, типы данных. Задачи содержат локальные и интерфейсные переменные и функции, тело. В отличие от языка Ада, в разработанном языке нет типов задач, имеются только их экземпляры (то есть задачи не могут создаваться динамически). Данное ограничение обусловлено малым количеством ресурсов, которые доступны программе, работающей в микроконтроллере.
В качестве импортируемых ресурсов могут быть указаны как сторонние пакеты, ссылки на файлы с которыми имеются в проекте, так и файлы на языке С. Полная грамматика языка в нотации Xtext [36] приведена в приложении Б.
Вместе с концепциями пакетов и задач в дополнение к грамматике языка С были добавлены конструкции, приведенные в таблице 2.2. Пример программы на текстовом языке приведен в приложении В на рисунке В.1.
Асинхронные API и объект Deferred в деталях
Большинство современных языков программирования позволяют использовать асинхронно выполняемые блоки кода. Вместе с гибкостью, получаемой при использовании асинхронного подхода, рискнувший его применить также получает более сложный для понимания и поддержки код. Однако, любое усложнение, с которым сталкиваются программисты, как правило, находит практическое решение в виде нового подхода или повышения уровня абстракции. В случае асинхронного программирования таким средством служит объект типа отложенный результат или deferred (англ. deferred — отложенный, отсроченный).
В статье будет рассказано о базовых подходах к возврату асинхронных результатов, функциях обратного вызова, объектах deferred и их возможностях. Будут приведены примеры на языке JavaScript, а также произведён разбор типового объекта deferred. Статья будет полезна программистам, начинающим постигать асинхронное программирование, а также знакомым с ним, но не владеющим объектом deferred.
Синхронный и асинхронный вызовы
Любую функцию можно описать в синхронном и асинхронном виде. Предположим, что у нас есть функция calc , выполняющая некоторое вычисление.
В случае обычного, «синхронного» подхода, результат вычисления будет передаваться через возвращаемое значение, то есть результат будет доступен сразу же после выполнения функции, и может быть использован в другом вычислении.
Код выполняется строго последовательно, результат полученный на одной строке может быть использован на следующей. Это напоминает доказательство теоремы, когда последующие утверждения логически вытекают из предыдущих.
В случае асинхронного вызова, мы не можем получить результат на месте. Вызывая функцию calc , мы лишь укажем на необходимость выполнить вычисление и получить его результат. При этом следующая строка начнёт выполняться не дожидаясь выполнения предыдущей. Тем не менее, получить результат нам как-то надо, и тут на помощь приходит коллбек (callback) — функция, которая будет вызвана системой по приходу результата вычисления. Результат будет передан в эту функцию как аргумент.
Из примера видно, что функция теперь имеет сигнатуру: calc(callback) , а callback принимает в качестве первого параметра результат.
Так как calc выполняется асинхронно, функция no_cares_about_result не сможет обратиться к её результату, и, вообще говоря, может быть выполнена раньше чем коллбек (если говорить конкретно о JavaScript — в случае если вызываемая функция истинно асинхронная, а не берёт данные из кеша, например — гарантируется, что она всегда будет выполнена после выполнения вызвавшего кода, то есть оставшийся код всегда будет выполняться раньше коллбека; об этом будет рассказано чуть ниже).
Согласитесь, такой код уже стал несколько сложнее для восприятия, при той же смысловой нагрузке, что и его «прямолиненый» синхронный аналог. В чём же выгода от использования асинхронного подхода? Прежде всего — в разумном использовании ресурсов системы. Например, если calc является трудоёмким вычислением, которое может затратить много времени, или использует какой-то внешний ресурс, на использование которого накладывается определённая задержка, то при синхронном подходе весь последующий код будет вынужден ожидать результата и не будет выполняться, пока не выполнится calc . Используя асинхронный подход можно явно указать какой участок кода зависит от некоторого результата, а какой к результату индифферентен. В примере, no_cares_about_result явно не использует результат, и, следовательно, ему не требуется его ожидать. Участок кода внутри коллбека же будет выполнен только после получения результата.
Вообще говоря, большинство API, по своей природе, являются асинхронными, но могут мимикрировать под синхронные: доступ к удалённым ресурсам, запросы к БД, даже файловое API — асинхронные. Если API «притворяется» синхронным, то успех такого «притворства» связан с задержками результата: чем меньше задержка, тем лучше. То же файловое API, работая с локальной машиной, показывает небольшие задержки и зачастую реализуется как синхронное. Работа с удалёнными ресурсами и доступ к БД всё чаще реализуется асинхронно.
Многоуровневые вызовы
Сложности асинхронного подхода становятся более заметными, когда необходимо не просто сделать асинхронный вызов, а, получив его результат, что-то с ним сделать и использовать в другом асинхронном вызове. Очевидно, что здесь не подходит синхронный подход в несколько последовательно выполняемых строчек кода:
Код приобретёт следующий вид:
Во-первых, данный код стал «многоуровневым», хотя, по выполняемым действиям он аналогичен синхронному. Во-вторых, в сигнатурах функций calc_two , calc_three смешаны входные параметры и коллбек, который, по сути является местом возврата результата, то есть выходным параметром. В-третьих, каждая функция может завершиться с ошибкой, и результат не будет получен.
Упростить данный код можно определением функций-коллбеков отдельно и передаче их по имени, однако, это не решение всех проблем. Тут требуется новый уровень абстракции, а именно, мы можем подвергнуть абстрагированию асинхронный результат.
Асинхронный результат
Что есть такой результат? По сути, это объект, содержащий информацию о том, что результат когда-нибудь придёт или уже пришёл. Подписка на результат осуществляется через всё тот же коллбек, однако теперь она инкапсулирована в этом объекте и не обязывает асинхронные функции реализовывать коллбеки как входные параметры.
По сути от объекта-результата требуется три вещи: реализовывать возможность подписки на результат, возможность указывать приход результата (это будет использоваться самой асинхронной функцией, а не клиентом API) и хранение этого результата.
Важной отличительной особенностью такого объекта также является специфика его состояний. Такой объект может быть в двух состояниях: 1) нет результата и 2) есть результат. Причём переход возможен только из первого состояния во второе. Когда результат будет получен, то уже невозможно перейти в состояние его отсутствия или в состояние с другим результатом.
Рассмотрим следующий простой интерфейс для данного объекта:
Метод on принимает коллбек. Коллбек будет вызван как только будет доступен результат и он будет передан в качестве параметра. Здесь полная аналогия с обычным коллбеком, передаваемым в качестве параметра. На момент регистрации коллбека объект может находиться в состоянии с результатом и без. В случае, если результата ещё нет, коллбек будет вызван по его приходу. В случае, если результат уже есть, коллбек будет вызван немедленно. В обоих случаях коллбек вызывается однократно и получает результат.
Метод resolve позволяет перевести (разрезолвить) объект в состояние с результатом и указать этот результат. Этот метод является идемпотентным, то есть повторные вызовы resolve не будут изменять объект. При переходе в состояние с результатом будут вызваны все зарегистрированные коллбеки, а все коллбеки, которые будут зарегистрованы после вызова resolve станут вызываться мгновенно. В обоих случаях (регистрация до и после вызова resolve ) коллбэки будут получать результат, в силу того, что объект хранит его.
Объект с таким поведением называется deferred (а также известен под именами promise и future). Перед простыми коллбеками он имеет ряд преимуществ:
1. Абстрагирование асинхронной функции от результата: теперь каждой асинхронной функции не требуется предоставлять параметры-коллбеки. Подписка на результат остаётся за клиентом кода. Например, можно вообще не подписываться на результат, если он нам не нужен (аналогичен передачи noop-функции в качестве коллбека). Интерфейс асинхронной функции становится чище: он имеет только значимые входные параметры, становится возможным более уверенно использовать функции с неопределённым числом параметров, параметром опций ит.д.
2. Абстрагирование от состояния результата: клиенту кода не нужно проверять текущее состояние результата, он просто подписывает обработчик и не задумывается, пришёл результат или ещё нет.
3. Возможность множественной подписки: можно подписать более одного обработчика и все они будут вызваны по приходу результата. В схеме с коллбеками пришлось бы создавать функцию, которая вызывает группу функций, например.
4. Ряд дополнительных удобств, в числе которых, например, «алгебра» объектов deferred, которая позволяет определять отношения между ними, запускать их в цепочке или после успешного завершения группы таких объектов.
Рассмотрим следующий пример. Пусть имеется асинхронная функция getData(id, onSuccess) , которая принимает два параметра: id некоторого элемента, который мы хотим получить и коллбек для получения результата. Типичный код её использования будет выглядеть так:
Перепишем это с использованием Deferred . Функция теперь имеет сигнатуру getData(id) и используется следующим образом:
В данном случае код практически не усложнился, скорее просто изменился подход. Результат теперь передаётся через возвращаемое значение функции в качестве deferred. Однако, как станет заметно далее, в более сложных случаях использование deferred даёт некоторое преимущество в читаемости кода.
Обработка ошибок
Резонным будет вопрос об обработке ошибок при использовании таких объектов. В синхронном коде широко используется механизм исключений, который позволяет в случае ошибки передавать управление в вышестоящие блоки кода, где все ошибки могут быть отловлены и обработаны, существенно не усложняя «местный» код, освобождая программиста от необходимости писать проверки на каждый чих.
В асинхронном коде (и в любой схеме с коллбеками) существует некоторое затруднение при использовании исключений, потому как исключение будет приходить асинхронно, как и результат, и потому его нельзя будет просто отловить, обрамив вызов асинхронной функции в try . Если рассмотреть ошибку, то по сути, это лишь иной результат функции (можно сказать, отрицательный, но тоже результат), при этом в качестве возвращаемого значения выступает объект ошибки (исключения).
Такой результат, также как и успешный, реализуется в виде коллбека (который иногда называется errback, игра слов от error и back).
Давайте усилим наш учебный объект Deferred так, чтобы он мог предоставлять подписку отдельно на успех и на неудачу, а именно переработаем методы on и resolve .
В качестве первого параметра можно передавать значение перечислимого типа с двумя значениями, например E_SUCCESS , E_ERROR . Для читаемости, будем использовать в примерах простые строковые значения: ‘success’, ‘error’. Также, усилим данный метод, обязав его возвращать сам объект Deferred . Это позволит использовать цепочки подписок (приём весьма характерный конкретно для JavaScript).
Соответственно изменяется и метод resolve :
В качестве первого параметра передаётся состояние, в которое должен перейти объект Deferred (error, success), а в качестве второго — результат. На такой модифицированный объект по-прежнему распространяется правило состояний: после перехода в состояние с результатом, объект не может изменить своё состояние на иное. Это означает, что если объект перешёл, например, в состояние success, то все обработчики, зарегистрированные на ошибку не сработают никогда, и наоборот.
Итак, пусть наша функция getData может завершиться с некоторой ошибкой (нет данных, неправильные входные данные, сбой и т.п.).
Код примет следующий вид:
Рассмотрим более реалистичный пример, а именно, возьмём типовой метод fs.readFile из стандартной библиотеки Node.js. Этот метод служит для чтения файла. В начале статьи упоминалось, что практически любую функцию можно написать либо в синхронном, либо в асинхронном стиле. В стандартной библиотеке Node.js файловое API определено в обоих стилях, у каждой функции есть свой синхронный аналог.
Для примера мы используем асинхронный вариант readFile и адаптируем его под использование Deferred.
Такая функция несколько удобней в использовании, потому как позволяет регистрировать функции на успех и на ошибку отдельно.
Описанной функциональности вполне достаточно для преобладающего большинства случаев, но deferred имеет больший потенциал, о чём будет рассказано ниже.
Расширенные возможности объектов Deferred
1. Неограниченное количество вариантов результата. В примере был использован объект Deferred с двумя возможными результатами: success и error. Ничто не мешает использовать любые другие (кастомные) варианты. Благо, мы использовали строковое значение в качестве state, это позволяет определять любой набор результатов, не изменяя никакой перечислимый тип.
2. Возможность подписки на все варианты результата. Это может быть использовано для разного рода обобщённых обработчиков (наибольший смысл это имеет вкупе с пунктом 1.).
3. Создание суб-объекта promise. Из интерфейса объекта Deferred видно, что клиентский код имеет доступ к методу resolve , хотя, по сути, ему требуется только возможность подписки. Суть данного улучшения состоит во введении метода promise , который возвращает «подмножество» объекта Deferred , из которого доступна только подписка, но не установка результата.
4. Передача состояния от одного deferred к другому, опционально, подвергая результат преобразованию. Это бывает очень полезным при многоуровневых вызовах.
5. Создание deferred, который зависит от результата набора других deferred. Суть данного улучшения в том, чтобы подписываться на результат группы асинхронных операций.
Пусть нам нужно зачитать два файла и сделать с обоими что-нибудь интересное. Используем нашу функцию readFileDeferred для этого:
Deferred.all создаёт новый объект Deferred , который перейдёт в состояние success, если все переданные аргументы перейдут в это состояние. При этом он также получит результаты всех deferred в качестве аргументов. Если хотя бы один аргумент перейдёт в состояние error, то и результат Deferred.all также перейдёт в это состояние, и получит в качестве результата результат аргумента, перешедшего в состояние error.
Особенности deferred в JavaScript
Стоит отметить тот момент, что в JavaScript отсутствует многопоточность. Если коллбек был установлен по setInterval / setTimeout или по событиям, он не может прервать выполнение текущего кода, или выполняться параллельно с ним. Это означает, что даже если результат асинхронной функции придёт мгновенно, он всё равно будет получен лишь после завершения выполнения текущего кода.
В JavaScript функции могут вызываться с любым числом параметров, а также с любым контекстом. Это позволяет передавать в коллбеки столько параметров, сколько потребуется. Например, если асинхронная функция возвращает пару значений (X, Y) , то их можно передать в виде объекта с двумя полями, или списка с двумя значениями (импровизированный аналог кортежа), а можно использовать два первых аргумента коллбека для этой цели.
Вызов коллбека в таком случае может принимать следующий вид:
В JavaScript используются ссылки, а освобождение памяти контролируется сборщиком мусора. Объект deferred нужен как внутри асинхронной функции (чтобы просигнализировать о приходе результата), так и снаружи (чтобы получить результат), в языках с более строгими моделями работы с памятью следует озаботиться правильной обработкой времени жизни такого объекта.
Sync vs Async
При проектировании микросервисной архитектуры нередко возникает вопрос, какой именно способ связи между микросервисами лучше использовать. Конечно, всегда можно отдать предпочтение RESTful API, что и делают в большинстве случаев. Но на практике такой подход эффективен не всегда, ведь в отдельных ситуациях возможно долгое ожидание со стороны клиента и потеря информации при сбоях. Однако существует и другой вариант взаимодействия между микросервисами: очереди сообщений.
Message Queue представляет собой форму асинхронной коммуникации между сервисами. Именно асинхронной. Чтобы понять разницу между асинхронным и синхронным взаимодействием, давайте приведем простой и немного искусственный пример.
Представьте, что вы работаете над сайтом книжного магазина, причем у вас есть сервис, к которому пользователь систематически обращается, к примеру, в целях отправки отзыва на прочитанную книгу. Когда он нажимает кнопку «Отправить» происходит вызов некоторого API, который, кстати, может обращаться и к другим API.
В случае синхронного взаимодействия все запросы в данной цепочке вызовов будут выполняться в строгой последовательности друг за другом, причем при выполнении последнего запроса ответы так же последовательно будут передаваться обратно. По итогу пользователь станет несколько секунд ожидать сообщения о публикации отзыва, хотя он, разумеется, был бы рад увидеть сообщение сразу же после нажатия кнопки (и правда, его ведь не интересуют особенности вашей серверной обработки). Естественно, время ожидания станет во многом определяться мощностью оборудования, однако при пиковых нагрузках это замедление может превратиться в серьезную проблему.
Но выше описан не единственный недостаток синхронного взаимодействия, так как существует и другой минус: обработка сбоев. Когда на одном из шагов возникает исключение, это исключение каскадно возвращается назад, а пользователь получает уведомление об ошибке и неприятную просьбу отправить рецензию повторно. Такое сообщение, да еще и после долгого ожидания, вряд ли кого обрадует.
То есть синхронное взаимодействие на основе REST API можно представить следующей схемой:

Но эту схему мы можем изменить, если добавим асинхронные вызовы. Нам просто достаточно вызвать первый REST API в асинхронном режиме, параллельно с этим вернув пользователю сообщение, что его отзыв принят и будет размещен, допустим, в течение ближайших суток. Таким образом веб-сайт блокироваться не будет, а вызовы всех дальнейших API станут происходить вне зависимости от пользователя.
Но и у вышеописанной схемы будет недостаток, причем довольно серьезный: а что если произойдет сбой в одном из API? В таком случае информация, которая была введена пользователем, вообще может оказаться потерянной. Причем если в первом примере наличие ошибки привело бы просто к повторной отправке отзыва, то во втором случае форму отзыва пришлось бы заполнять заново, а это уже совсем никуда не годится.
REST API: возможный вариант асинхронного взаимодействия:

Как устранить недостатки обеих схем? Как раз для этого и нужны очереди сообщений (Message Queues). Но о них мы поговорим в следующий раз, следите за обновлениями блога!
Синхронные и асинхронные запросы
XMLHttpRequest поддерживает как синхронную, так и асинхронную связь. В целом, однако, асинхронные запросы следует предпочесть синхронным запросам из соображений производительности.
Синхронные запросы блокируют выполнение кода,что приводит к «замиранию» на экране и невосприимчивости пользователя.
Asynchronous request
Если вы используете асинхронный XMLHttpRequest , вы получите обратный вызов, когда данные будут получены. Это позволяет браузеру продолжать работать в обычном режиме, пока ваш запрос обрабатывается.
Пример:отправить файл в консольный журнал
Это простейшее использование асинхронного XMLHttpRequest .
Строка 2 указывает true для своего третьего параметра, чтобы указать, что запрос должен обрабатываться асинхронно.
Строка 3 создает объект функции обработчика событий и назначает его атрибуту onload запроса . Этот обработчик смотрит на состояние readyState запроса, чтобы увидеть, завершена ли транзакция в строке 4; если это так и статус HTTP равен 200, обработчик выгружает полученное содержимое. Если произошла ошибка, отображается сообщение об ошибке.
На самом деле,15-я строка инициирует запрос.Обратный вызов вызывается всякий раз,когда состояние запроса меняется.
Пример:написание функции для чтения внешнего файла
В некоторых случаях вам необходимо прочитать много внешних файлов. Это стандартная функция, которая асинхронно использует объект XMLHttpRequest для переключения содержимого прочитанного файла на указанный прослушиватель.
Подпись служебной функции loadFile объявляет (i) целевой URL-адрес для чтения (через HTTP-запрос GET), (ii) функцию, выполняемую при успешном завершении операции XHR, и (iii) произвольный список дополнительных аргументов, которые передаются через объект XHR (через свойство arguments ) в функцию обратного вызова успеха.
Строка 1 объявляет функцию, вызываемую после успешного завершения операции XHR. Он, в свою очередь, вызывает функцию обратного вызова, указанную при вызове функции loadFile (в данном случае функции showMessage ), которая была назначена свойству объекта XHR (строка 11). Дополнительные аргументы (если есть), предоставленные для вызова функции loadFile, «применяются» к запуску функции обратного вызова.
В пятой строке объявляется функция,вызываемая при неудачном завершении операции XHR.
Строка 11 хранит успешный обратный вызов, указанный как второй аргумент loadFile в свойстве callback объекта XHR .
Строка 12 loadFile массив аргументов, переданный при вызове loadFile . Начиная с третьего аргумента, все оставшиеся аргументы собираются, присваиваются свойству arguments переменной xhr , передаются в функцию обратного вызова успеха xhrSuccess . И в конечном итоге передаются функции обратного вызова (в данном случае showMessage ), которая вызывается функцией xhrSuccess .
Строка 15 указывает true для своего третьего параметра, чтобы указать, что запрос должен обрабатываться асинхронно.
Строка 16 фактически инициирует запрос.
Пример:использование таймаута
Вы можете использовать тайм-аут, чтобы ваш код не зависал в ожидании завершения чтения. Это делается путем установки значения свойства timeout в объекте XMLHttpRequest , как показано в приведенном ниже коде:
Обратите внимание на добавление кода для обработки события «timeout», установив обработчик ontimeout .
Здесь мы указываем тайм-аут на 2000 мс.
Примечание: поддержка timeout была добавлена в Gecko 12.0.
Synchronous request
Примечание. Начиная с Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), Blink 39.0 и Edge 13, синхронные запросы в основном потоке устарели из-за их негативного влияния на взаимодействие с пользователем.
Синхронные запросы XHR часто вызывают зависания в Интернете.Но разработчики обычно не замечают этой проблемы,потому что зависание проявляется только при плохих сетевых условиях или когда удаленный сервер медленно отвечает.Синхронный XHR в настоящее время находится в состоянии устаревания.Разработчикам рекомендуется отказаться от синхронного API и вместо него использовать асинхронные запросы.
Все новые функции XHR, такие как timeout или abort , не разрешены для синхронного XHR. Это вызовет InvalidAccessError .
Пример:синхронный запрос HTTP
Данный пример демонстрирует,как сделать простой синхронный запрос.
Строка 3 отправляет запрос. null параметр указывает , что содержимое тела не требуется для GET запроса.
В пятой строке проверяется код статуса после завершения транзакции.Если результат 200 —HTTP «OK» —текстовое содержимое документа выводится на консоль.
Пример:Синхронный HTTP-запрос от рабочего устройства
Один из немногих случаев, когда синхронный запрос обычно не блокирует выполнение, — это использование XMLHttpRequest в Worker ‘е .
example.html (главная страница):
myFile.txt (цель синхронного вызова XMLHttpRequest ):
Примечание . Эффект асинхронный из-за использования Worker .
Этот шаблон может быть полезен, например, для взаимодействия с сервером в фоновом режиме или для предварительной загрузки содержимого. См. Примеры и подробности в разделе « Использование веб- воркеров».
Адаптация сценариев использования Sync XHR к API Beacon
В некоторых случаях синхронное использование XMLHttpRequest невозможно заменить, например, во время событий unload , beforeunload и pagehide . Вам следует рассмотреть возможность использования API fetch() с флагом keepalive . Когда fetch с keepalive недоступна, вы можете рассмотреть возможность использования API navigator.sendBeacon() , который может поддерживать эти варианты использования, но обычно обеспечивает хороший UX.
В следующем примере показан код теоретической аналитики, который пытается отправить данные на сервер с помощью синхронного XMLHttpRequest в обработчике выгрузки. Это приводит к задержке выгрузки страницы.
Используя метод sendBeacon() , данные будут передаваться асинхронно на веб-сервер, когда пользовательский агент сможет это сделать, не задерживая выгрузку и не влияя на производительность следующей навигации.
В следующем примере показан шаблон кода теоретической аналитики, который отправляет данные на сервер с помощью sendBeacon() .