CoderCastrov logo
CoderCastrov
Асинхронность

Параллелизация циклов загрузки в JS с использованием async-await-queue

Параллелизация циклов загрузки в JS с использованием async-await-queue
просмотров
2 мин чтение
#Асинхронность
Table Of Content

    Сколько раз вы сталкивались с классической проблемой параллелизации циклов загрузки в парсерах и скраперах?

    Рассмотрим следующий типичный код парсера или скрапера:

    Благодаря async / await это просто и читаемо.

    Но что, если у вас тысячи URL-адресов? Это займет вечность, так как каждая итерация ждет завершения предыдущей.

    К счастью, в JS есть решение:

    Здесь мы запускаем все загрузки без await. Красота цепочки обещаний позволяет нам создавать новые обещания из последовательности асинхронных операций. Мы добавляем все эти обещания в массив, а затем ожидаем параллельного завершения всех них. Важно использовать allSettled, так как он не остановится на первом отклоненном обещании, сохраняя тем самым поведение исходного кода, который продолжал работать, даже если некоторые URL-адреса не удалось загрузить.

    Теперь вы можете запустить это и просто расслабиться. То есть, пока не придет администратор сети и не начнет кричать. Одной из основных причин успеха Node.js была его особенно эффективная подсистема ввода-вывода, и этот код легко может сбить с толку сервер высокого уровня, если запускать его с ноутбука. Или, что еще хуже, это может быть общедоступный API, и следующее сообщение, которое вы получите, будет "Ваш IP-адрес был навсегда заблокирован из-за злоупотребления".

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

    В этом случае на помощь приходит async-await-queue. Он позволяет легко контролировать уровень агрессивности вашего парсера, чтобы он работал быстро, но при этом был относительно дружелюбным или, по крайней мере, не слишком раздражал окружающих.

    Мы создаем очередь async-await-queue, которая позволяет одновременно загружать не более 10 элементов, с интервалом не менее 100 мс. Эти настройки обычно близки к максимально возможным, чтобы не быть заблокированным при доступе к общедоступному API.

    Мы создаем новые обещания, цепляя асинхронные операции. На этот раз мы начинаем с ожидания нашего места в очереди. В конце, всегда в блоке finally, мы освобождаем наше место для следующей операции. Если операция завершается без вызова end(), ее место остается навсегда занятым.

    Теперь это легко и достаточно просто.

    Но вся суть async / await заключается в том, чтобы избежать ужасной цепочки then/catch/finally и сделать сложный код более читаемым. Что, если у нас есть условные выражения?

    В этом нам поможет async:

    Каждая итерация - это анонимная асинхронная функция, которая объявляется и сразу вызывается. Мы добавляем ее возвращаемое значение, Promise, в p[].

    Этот код эквивалентен предыдущему. Однако использование анонимной асинхронной функции делает цепочку более интуитивно понятной для чтения и позволяет использовать естественные условные операторы.

    В надежде, что это будет полезно, async-await-queue доступен в npm под лицензией MIT.