Или почему нужно быть осторожным при работе с потоками и эмиттерами событий вообще

Пример

Чтобы продемонстрировать эту проблему, я буду использовать потоки Node.js:

Поток — это структура, которая позволяет пользователям последовательно читать/записывать данные из/в нее. Потоки в основном используются при работе с большими объемами данных. Когда вы хотите переместить большой кусок данных из одного места в другое (например, из файла в новый файл), лучше использовать потоковую передачу, поэтому вам не нужно хранить все данные в памяти (это может быть даже невозможно, если данные слишком большой). "Читать далее".

Мы создаем поток Writable (тот, который разрешает только запись данных) и сразу после этого уничтожаем его. Функция destroy() принимает в качестве параметра ошибку, которая возникает при использовании потока после уничтожения. Затем мы пытаемся использовать поток и ожидаем получить ошибку в блоке catch.

Как вы уже догадались, он не пойман!

Что происходит?

Ответ на этот вопрос заключается в том, что поток является подклассом EventEmitter object, который использует асинхронный обратный вызов.

Хм, а что это значит? Что это EventEmitter? Почему он такой особенный?

Генератор событий

Генератор событий — это объект, предоставляемый Node.js для работы с системой событий (это часть встроенного пакета событий). Это позволяет нам отправлять события и обрабатывать их с помощью слушателей. Код говорит больше, чем тысячи слов, поэтому вот пример использования базового эмиттера событий:

Мы создаем новый экземпляр EventEmitter и присоединяем обработчик событий к событию 'myEvent'. Теперь каждый раз, когда выдается 'myEvent', будет вызываться функция eventHandler(). Затем мы генерируем это событие и ожидаем, что обработчик будет запущен.

И результат, как и ожидалось:

Что, если мы выдадим ошибку внутри обработчика? И окружить все try-catch. Давайте посмотрим:

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

И после запуска скрипта мы видим, что ошибка неотлавливается:

Если подумать, это логичное поведение. Мы отправляем асинхронный обработчик и нигде не ждем его (и его использования). Обратный вызов генератора событий вызывается независимо от try-catch, поэтому он никогда не перехватывается.

Как сделать так, чтобы приложение не вылетало?

Обработка ошибок эмиттера событий

Объекты генератора событий дают нам специальное событие для обработки ошибок. Каждый раз, когда внутри экземпляра генератора событий возникает ошибка, генерируется специальное событие 'error'. Но нам нужно неявно попросить генератор событий действовать таким образом, отправив { captureRejections: true } внутри конструктора.

Все, что нам нужно сделать сейчас, это прикрепить обратный вызов к событию 'error':

Теперь ни одно приложение не вылетает.

Исправление примера потоков

Помните первый пример? Теперь это должно быть легко исправить с этим знанием. Для потоков нам не нужно устанавливать captureRejection: true. Он установлен по умолчанию.

Все, что нужно, это обратный вызов для события 'error':

Небольшое изменение кода, которое может предотвратить сбои вашего производственного кода в непредсказуемые моменты.

Как избежать этих необработанных ошибок?

Довольно просто, вам просто нужно настроить обратный вызов для события 'error'. Настоящий вопрос заключается в том, как узнать, работаете ли вы с подклассом генератора событий?

Есть несколько встроенных, хорошо известных примеров эмиттеров событий:

  • Стримы (require('stream')) — о них уже говорили
  • Сеть (require('net')) — используется для работы в сети
  • CreateReadStream() из пакета fs ({createReadStream} = require(‘fs’)) — используется для создания потоков из файлов (относится к категории потоков, но настолько популярен, что я решил отметить его здесь).

По сути, все объекты, генерирующие события, являются экземплярами генерирующих события.

Чтобы быть уверенным, вы можете изучить реализацию и проверить, расширяет ли EventEmitter или имеет EventEmitter в цепочке prototype. Также будет полезно ознакомиться с документацией.

Заключительные заметки

Мы начали с упрощенного, но реального примера. Какой-то разработчик использовал поток внутри try-catch и был уверен, что приложение никогда не вылетит. Несколько раз в неделю приложение вылетало без причины. Он усердно работал, чтобы найти проблему, но понятия не имел.

«Место, где происходит сбой, находится внутри try-catch. Его надо поймать! Куда проскальзывают эти ошибки?»

Пока не сообразил про эмиттеры событий и все сразу стало понятно.

Надеюсь, ты не совершишь эту ошибку.

Генераторы событий — очень интересная тема. Читать далее проверь.

Создавайте компонуемые веб-приложения

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых средах, таких как React или Node. Создавайте масштабируемые и модульные приложения с мощными и приятными возможностями разработки.

Перенесите свою команду в Bit Cloud, чтобы вместе размещать компоненты и совместно работать над ними, а также ускорять, масштабировать и стандартизировать разработку в команде. Попробуйте компонуемые внешние интерфейсы с помощью Design System или Микроинтерфейсы или изучите компонуемые внутренние интерфейсы с серверными компонентами. .

Попробуйте →

Узнать больше