Руководство, которое поможет вам понять асинхронное поведение Node JS.

Как backend-разработчик, я понимаю, как люди путаются между асинхронным и синхронным поведением NodeJS. В этом блоге я объясню асинхронное поведение NodeJS и отвечу на все ваши КАК и ПОЧЕМУ!

Отбросьте путаницу и читайте дальше. Приступим!

Если вы прочитаете любую статью о NodeJS, вы найдете что-то очень распространенное. Эти четыре термина описывают природу NodeJS- асинхронный, неблокирующий, однопоточный и управляемый событиями. В модели программирования Node почти все выполняется асинхронно, но многие функции ядра NodeJS имеют как синхронную, так и асинхронную версии.

Давайте сначала разберемся, что такое модель синхронного программирования.

МОДЕЛЬ СИНХРОННОГО ПРОГРАММИРОВАНИЯ

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

Очень простой и простой пример:

console.log("start")
console.log("end")

На выходе сначала будет напечатано начало, затем - конец. Это показывает последовательное выполнение кода.

Давайте поясним еще на одном примере:

var fs = require('fs');
var content = fs.readFileSync("file.txt", "utf8");
console.log(content);
console.log("Last One");

readFileSync () прочитает весь файл и сохранит его в памяти, а затем перейдет к печати данных и сообщения в консоли (см. вывод). Это синхронная версия, все приостанавливается до завершения чтения файла.

МОДЕЛЬ АСИНХРОННОГО ПРОГРАММИРОВАНИЯ

Код JavaScript выполняется в одном потоке компьютерного процесса. Его код обрабатывается синхронно в этом потоке, при этом одновременно выполняется только одна инструкция. Следовательно, если бы мы выполняли длительную задачу в этом потоке, весь оставшийся код блокировался до тех пор, пока задача не была завершена. Используя возможности асинхронного программирования JavaScript, мы можем переложить длительные задачи в фоновый поток, чтобы избежать этой проблемы. Когда задача завершена, код, необходимый для обработки данных задачи, возвращается в основной единственный поток.

В асинхронном программировании мы также можем выполнить другой код, пока мы ждем полного завершения долгих действий.
Пример: получение данных для пользователя из базы данных.

Давайте посмотрим на тот же пример чтения файла, который мы видели выше, но теперь с использованием асинхронной модели:

var fs = require('fs');
fs.readFile("file.txt", "utf8", function(err, content) {
if (err) {
      return console.log(err);
	     }
  console.log(content);
      });
console.log("Last One");

Это асинхронная версия. Здесь система готова к выполнению другой задачи. Вместо этого функция обратного вызова вызывается по завершении чтения файла. См. Вывод: сначала выполняется последняя команда и печатается Last One, затем печатается содержимое файла. Это связано с тем, что чтение содержимого из файла займет некоторое время, в течение этого интервала будут выполняться следующие строки кода. Это показывает неблокирующий характер NodeJS.

Асинхронное программирование с обратными вызовами

Функция обратного вызова - это функция, которая передается в качестве аргумента другой функции и затем выполняется, когда другая функция завершается. Мы используем обратные вызовы, чтобы гарантировать, что код выполняется только после завершения асинхронной операции.

Если вы заметили в приведенном выше коде, readFile () принимает третий параметр, который является функцией обратного вызова. И внутри этой функции обратного вызова мы отображаем содержимое, прочитанное из файла. Таким образом, обратный вызов будет выполнен, как только процесс чтения файла будет завершен, а затем будет отображено содержимое файла.

Давайте сделаем понимание асинхронного поведения более ясным и углубимся в ответы на все вопросы «КАК» и «ПОЧЕМУ?».

Цикл событий

Некоторые важные термины, связанные с работой цикла событий:

  1. Стек вызовов - это структура данных в виде списка, в которой элементы можно добавлять только вверху и удалять сверху. Его задача - отслеживать выполнение программы, и он делает это, отслеживая выполняемые в данный момент функции.
  2. Таблица хранения (иногда называемая контроллером API узла). В этой таблице хранится операция, условие ее завершения и функция, которая будет вызываться по завершении.
  3. Очередь обратного вызова. Эта очередь представляет собой еще одну структуру данных в виде списка, в которой элементы можно добавлять только снизу, но удалять сверху. Функции в очереди обратного вызова ожидают добавления в стек вызовов.

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

console.log('Starting');
setTimeout(()=>{
console.log("2 second timer")
},2000);
console.log("Ending");

  • Первое, что происходит, - это то, что main () добавляется в стек вызовов, который запускает выполнение нашей программы.
  • Затем элемент управления достигает первой строки кода, которая представляет собой команду console.log (). Поскольку это функция, она добавляется в стек вызовов. Затем выводится сообщение «Запуск», и после этого console.log () удаляется из стека вызовов.
  • Переходя к следующей строке, мы видим setTimeout (…, 2000), который также является функцией. Таким образом, он также будет помещен в стек вызовов. setTimeout () не является частью языка программирования javascript, вместо этого это NodeJS, который создает реализацию setTimeout () с использованием C ++ и предоставляет ее сценариям NodeJS для использования. По сути, это асинхронный способ подождать определенное время, а затем запустить функцию. Когда мы вызываем setTimeout (), это фактически регистрирует событие с помощью API-интерфейсов NodeJS, и существует пара обратных вызовов событий, где событие просто ожидает 2 секунды, а обратный вызов - это функция, которую нужно запустить.
  • Пока мы ждем этих двух секунд, мы можем делать другие вещи внутри стека вызовов. Итак, теперь последняя строка кода, которая представляет собой команду console.log (), теперь будет добавлена ​​в стек вызовов для выполнения. Вывод - «Конец» будет отображаться, и после этого console.log () будет удалена из стека. Поскольку теперь код закончен, main () будет удален из стека, и теперь стек пуст.
  • Теперь, когда 2 секунды истекли, обратный вызов готов к выполнению. Для этого он будет добавлен в очередь обратного вызова. Перед тем, как обратный вызов будет выполнен, его необходимо добавить в стек вызовов, где будут выполняться функции.
  • Здесь в игру вступает цикл событий. Он смотрит на две вещи - стек вызовов и очередь обратного вызова. Если он обнаруживает, что стек вызовов пуст, он запускает элементы из очереди обратного вызова. Поскольку теперь стек вызовов пуст, функция обратного вызова будет добавлена ​​в стек вызовов из очереди обратных вызовов, а затем будет выполнена. Эта функция обратного вызова содержит команду console.log (), которая сначала будет добавлена ​​в стек вызовов, затем вывод - «2-секундный таймер» будет отображаться, а затем он будет удален из стека.

Вот как мы получили такой результат.

Этот аналогичный пример вместо setTimeout () можно рассматривать как операцию по извлечению сведений о n людях из базы данных, которая имеет функцию обратного вызова, которую можно использовать для печати извлеченных данных. Функция обратного вызова выполняется только после завершения операции выборки данных.

Параллелизм и пропускная способность

Выполнение JavaScript в NodeJS является однопоточным, поэтому под параллелизмом понимается способность цикла событий выполнять функции обратного вызова JavaScript после завершения другой работы. Любой код, который, как ожидается, будет выполняться одновременно, должен позволять циклу обработки событий продолжать работу, пока выполняются не-JavaScript операции, такие как ввод-вывод.

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

Я надеюсь, что после прочтения этого блога вы почувствуете себя более уверенно и осведомлены о асинхронном программировании в NodeJS.

Это еще не конец…
Я работаю над другими блогами, чтобы помочь вам.