Мы все слышали об однопоточности JavaScript и Node.js, но что это означает на практике?

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

Мы все пытались собрать данные с этого медленного веб-сайта или ждали более тридцати секунд, прежде чем получить результат запроса к базе данных. Хотим ли мы заблокировать наш единственный поток от выполнения большего количества задач из-за медленного запроса к базе данных? К счастью, Node.js не прекращает выполнение других операций из-за Libuv, библиотеки C++, отвечающей за цикл обработки событий и асинхронную обработку таких задач, как сетевые запросы, разрешение DNS, операции с файловой системой, шифрование данных и т. д.

Что происходит внутри, когда Node.js работает над такими задачами, как запросы к базе данных? Мы изучим его, шаг за шагом следуя этому фрагменту кода.

Пример кода для демонстрации цикла событий

Движок JavaScript V8 управляет стеком вызовов, важной частью, которая отслеживает, какая часть нашей программы выполняется. Всякий раз, когда мы вызываем функцию JavaScript, она помещается в стек вызовов. Как только функция достигает своего конца или return оператор, он извлекается из стека.

В нашем примере строка кода console.log('Starting Node.js') добавляется в стек вызовов и печатает Starting Node.js к консоли. Таким образом, он достигает конца log функции и удаляется из стека вызовов.

Вызов функции в стеке вызовов Node.js

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

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

Libuv обрабатывает операции ввода-вывода

Пока Libuv обрабатывает запрос в фоновом режиме, наш JavaScript не блокируется и может продолжаться с console.log(”Before query result”).

Обработка ввода-вывода, пока Node.js запускает наш код

Когда запрос выполнен, его обратный вызов помещается в очередь событий ввода-вывода для выполнения в ближайшее время*.* Цикл событий соединяет очередь со стеком вызовов. Он проверяет, пуст ли последний, и перемещает первый элемент очереди на выполнение.

Цикл событий проверяет наличие пустого стека вызовов

Код доступен по адресу


Популярная викторина в цикле событий

Попробуйте выяснить, что следующий код выводит на консоль.

Более сложный пример кода для демонстрации цикла событий

Анимированное руководство по циклу событий


Вывод

Цикл событий, делегирование и механизм асинхронной обработки — это секретные ингредиенты Node.js для обработки тысяч подключений, чтения/записи гигантских файлов, обработки таймеров при работе с другими частями нашего кода.

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

Если вам понравилась статья, подпишитесь на нас в Твиттере @fabriziollo и @andrewhu368.