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

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

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

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

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

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

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

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

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

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

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

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

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

Здесь на помощь приходит асинхронный режим:

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

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

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