Node.js - Цикл событий: Раскрытие магии за асинхронным JavaScript

Здравствуйте, будущие кодировщики! Сегодня мы отправимся в увлекательное путешествие в сердце Node.js - Цикл событий. Не волнуйтесь, если вы никогда не писали ни строчки кода; я буду вашим доброжелательным гидом по этому fascинирующему миру. К концу этого учебника вы поймете, как Node.js manages чтобы сделать так много вещей одновременно, как вы жонглируете домашним заданием, Netflix и отправкой сообщений своим друзьям!

Node.js - Event Loop

Что такое Цикл событий?

Представьте, что вы шеф-повар в忙ном ресторане. У вас сразу готовятся несколько блюд, тикают таймеры, и поступают заказы. Как вы справляетесь со всем этим, не поджарив食物 или не заставляя клиентов ждать? Это то, что делает Цикл событий для Node.js!

Цикл событий похож на главного шеф-повара, который постоянно проверяет, что требует внимания и обеспечивает бесперебойную работу. Это secret sauce, который позволяет Node.js выполнять неблокирующие операции ввода-вывода, несмотря на то, что JavaScript является однониточным.

Основные концепции

Прежде чем мы углубимся, давайте familiarize ourselves с некоторыми основными концепциями:

  1. Однониточный: JavaScript работает на одной нити, что означает, что он может делать только одно дело за раз.
  2. Неблокирующий: Node.js может обрабатывать несколько операций, не дожидаясь завершения каждой из них перед переходом к следующей.
  3. Асинхронный: Задачи могут быть начаты сейчас и завершены позже, позволяя другому коду работать в это время.

Как работает Цикл событий?

Давайте разберем Цикл событий наdigestible шаги:

  1. Выполнение синхронного кода в стеке вызовов
  2. Проверка таймеров (setTimeout, setInterval)
  3. Проверка pending I/O операций
  4. Выполнение callbacks setImmediate
  5. Обработка событий 'close'

Теперь давайте посмотрим это в действии с некоторыми примерами кода!

Пример 1: Синхронный vs. Асинхронный код

console.log("First");

setTimeout(() => {
console.log("Second");
}, 0);

console.log("Third");

Что вы думаете, какой будет вывод? Давайте разберем это:

  1. "First" выводится немедленно.
  2. setTimeout встречается, но вместо ожидания, Node.js устанавливает таймер и продолжает.
  3. "Third" выводится.
  4. Цикл событий проверяет завершенные таймеры и выполняет callback, выводя "Second".

Вывод:

First
Third
Second

Удивлены? Это демонстрирует, как Node.js обрабатывает асинхронные операции, не блокируя основную нить.

Пример 2: Множественные таймеры

setTimeout(() => console.log("Timer 1"), 0);
setTimeout(() => console.log("Timer 2"), 0);
setTimeout(() => console.log("Timer 3"), 0);

console.log("Hello from the main thread!");

В этом примере мы устанавливаем несколько таймеров с задержкой 0 миллисекунд. Однако Цикл событий все равно обработает их после завершения основной нити.

Вывод:

Hello from the main thread!
Timer 1
Timer 2
Timer 3

Фазы Цикла событий

Теперь, когда мы видели Цикл событий в действии, давайте рассмотрим его фазы более подробно:

1. Фаза таймеров

Эта фаза выполняет callbacks, запланированные setTimeout() и setInterval().

setTimeout(() => console.log("I'm a timer!"), 100);
setInterval(() => console.log("I repeat every 1 second"), 1000);

2. Фаза pending callbacks

Здесь Цикл событий выполняет I/O callbacks, отложенные до следующей итерации цикла.

3. Фаза Idle, Prepare

Для внутреннего использования. Здесь нет ничего интересного!

4. Фаза Poll

Извлекает новые I/O события и выполняет related callbacks.

const fs = require('fs');

fs.readFile('example.txt', (err, data) => {
if (err) throw err;
console.log(data);
});

5. Фаза Check

setImmediate() callbacks вызываются здесь.

setImmediate(() => console.log("I'm immediate!"));

6. Фаза Close Callbacks

Some close callbacks, например, socket.on('close', ...), обрабатываются здесь.

Объединяем все вместе

Давайте создадим более сложный пример, который использует различные аспекты Цикла событий:

const fs = require('fs');

console.log("Start");

setTimeout(() => console.log("Timeout 1"), 0);
setImmediate(() => console.log("Immediate 1"));

fs.readFile('example.txt', (err, data) => {
console.log("File read complete");
setTimeout(() => console.log("Timeout 2"), 0);
setImmediate(() => console.log("Immediate 2"));
});

console.log("End");

Порядок выполнения может удивить вас:

  1. "Start" и "End" выводятся немедленно.
  2. Первый setTimeout и setImmediate ставятся в очередь.
  3. Операция чтения файла начинается.
  4. Цикл событий начинает свои итерации:
  • Выполняется callback первого setTimeout.
  • Выполняется callback первого setImmediate.
  • Когда чтение файла завершается, его callback выполняется.
  • Внутри callback чтения файла еще один setTimeout и setImmediate ставятся в очередь.
  • Второй setImmediate выполняется перед вторым setTimeout.

Общие методы Цикла событий

Вот таблица общих методов, связанных с Циклом событий, в Node.js:

Метод Описание
setTimeout(callback, delay) Выполняет callback после delay миллисекунд
setInterval(callback, interval) Выполняет callback repetitively каждый interval миллисекунд
setImmediate(callback) Выполняет callback на следующей итерации Цикла событий
process.nextTick(callback) Добавляет callback в "next tick queue", который обрабатывается после завершения текущей операции

Заключение

Поздравляю! Вы только что сделали свои первые шаги в fascинирующем мире Node.js и его Цикла событий. Помните, как обучение езде на велосипеде, овладение асинхронной программированием требует практики. Не отчаивайтесь, если это не сразу срабатывает - продолжайте экспериментировать, и скоро вы будете писать неблокирующий код как профессионал!

Заканчивая, вот забавная аналогия: подумайте о Цикле событий как о карусели. Разные задачи (например, таймеры, I/O операции и immediate callbacks) resemble дети, пытающиеся сесть. Цикл событий продолжает вращаться, picking up и dropping off задачи в определенном порядке, обеспечивая, чтобы каждый получил turn без остановки rides.

Продолжайте программировать, stay curious, и помните - в мире Node.js, терпение не просто добродетель, это callback!

Credits: Image by storyset