Форум программистов, компьютерный форум, киберфорум
run.dev
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Как работает Node.js изнутри

Запись от run.dev размещена 29.03.2025 в 18:46
Показов 5983 Комментарии 0
Метки async, javascript, node.js, v8

Нажмите на изображение для увеличения
Название: 127c1868-d473-4b0a-a3d6-03b8ef516f55.jpg
Просмотров: 197
Размер:	186.8 Кб
ID:	10496
Node.js изменил подход к разработке веб-приложений, позволив использовать JavaScript не только на стороне клиента, но и на сервере. Созданный в 2009 году Райаном Далем, этот открытый, кроссплатформенный runtime превратился в основной инструмент современного веб-разработчика. Сегодня трудно представить веб-разработку без Node.js — от стартапов до крупных корпораций вроде Netflix, PayPal или LinkedIn, многие полагаются на его возможности.

Но что же делает Node.js таким особенным и почему он завоевал такую популярность? Секрет кроется в его внутренней архитектуре и неблокирующей модели выполнения кода. В то время как традиционные серверные технологии обрабатывают запросы последовательно, Node.js использует событийно-ориентированный подход, который позволяет обрабатывать многочисленные операции параллельно. Чтобы понять гениальность идеи Райана Даля, нужно вернуться к проблемам, которые существовали в веб-разработке в конце 2000-х. Традиционные серверы, такие как Apache, создавали отдельный поток для каждого подключения. При большом количестве одновременных пользователей это приводило к существенным затратам ресурсов и снижению производительности. Даль заметил, что большую часть времени эти потоки просто "ждали" — ждали завершения операций ввода-вывода, таких как чтение с диска или запрос к базе данных.

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

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Пример неблокирующего кода в Node.js
const fs = require('fs');
 
// Асинхронное чтение файла, не блокирующее основной поток
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});
 
// Этот код выполнится сразу, не дожидаясь чтения файла
console.log('Продолжаем выполнение программы...');
Такой подход к обработке ввода-вывода известен как "неблокирующий ввод-вывод" или "асинхронный ввод-вывод". В основе Node.js лежит библиотека libuv, которая обеспечивает асинхронный интерфейс к операциям ввода-вывода на разных операционных системах, а также Google V8 — высокопроизводительный движок JavaScript, первоначально разработанный для браузера Chrome.

Выбор однопоточной модели для Node.js был осознанным решением, направленным на решение конкретной проблемы — эффективной обработки многочисленных одновременных соединений с минимальными накладными расходами. Многопоточные модели, используемые в других серверных технологиях, имеют свои преимущества, особенно для вычислительно интенсивных задач, но они также несут с собой сложности, связанные с синхронизацией потоков и разделяемым состоянием. Node.js, напротив, предлагает более простую модель программирования, где разработчик не должен заботиться о блокировках и состоянии гонки (race conditions). Однако эта простота имеет свою цену: выполнение вычислительно интенсивных операций в главном потоке может заблокировать событийный цикл и снизить отзывчивость всего приложения. В последних версиях Node.js эту проблему частично решают с помощью Worker Threads API, который позволяет выполнять JavaScript-код в отдельных потоках.

Компоненты системы



Чтобы понять, как Node.js справляется с асинхронными операциями, нужно разобраться в ключевых компонентах, из которых он состоит. В сердце Node.js находятся движок V8, библиотека libuv, а также система модулей и управления памятью — все эти элементы формируют мощный фундамент для создания высокопроизводительных приложений.

V8 и его роль в интерпретации JavaScript



Движок V8, разработанный Google для браузера Chrome, — первый краеугольный камень архитектуры Node.js. Этот высокопроизводительный интерпретатор JavaScript, написанный на C++, превращает код JavaScript в машинный код, а не просто интерпретирует его. Такой подход существенно ускоряет выполнение программ. V8 использует технологию Just-In-Time (JIT) компиляции, которая анализирует код во время выполнения и оптимизирует часто используемые функции. Вместо просто интерпретации скрипта строка за строкой, V8 компилирует JavaScript напрямую в нативный машинный код перед выполнением, что значительно повышает скорость работы.

Одно из ключевых преимуществ V8 — эффективный сборщик мусора (garbage collector), который автоматически освобождает память, когда объекты становятся недоступными. V8 использует генерационный подход к сборке мусора, разделяя объекты на "молодые" и "старые" поколения, что позволяет оптимизировать процесс очистки памяти.

JavaScript
1
2
3
4
5
6
7
8
9
10
// V8 оптимизирует такой код на лету
function sum(a, b) {
  return a + b;
}
 
// При многократном вызове с числами V8 может оптимизировать
// эту функцию специально для работы с числами
for (let i = 0; i < 100000; i++) {
  sum(i, i+1);
}
V8 также использует так называемые "скрытые классы" (hidden classes) — внутренние структуры, которые помогают оптимизировать доступ к свойствам объектов. Когда код создаёт много объектов с одинаковой структурой, V8 может существенно ускорить доступ к их свойствам.

Libuv и управление асинхронными операциями



Вторым фундаментальным компонентом Node.js является библиотека libuv — кроссплатформенная C-библиотека, отвечающая за абстрагирование неблокирующих операций ввода-вывода. Именно libuv делает возможным асинхронное выполнение кода в Node.js, независимо от операционной системы.
Libuv предоставляет не только событийный цикл, но и пул потоков для выполнения тяжёлых задач, которые нельзя выполнить асинхронно на уровне операционной системы. Это особенно важно для файловых операций в некоторых операционных системах, где нет нативной поддержки асинхронных файловых операций.

JavaScript
1
2
3
4
5
6
7
8
const fs = require('fs');
 
// Эта операция делегируется libuv, которая использует потоки из пула
// для выполнения чтения файла без блокирования основного потока
fs.readFile('/path/to/file', (err, data) => {
  if (err) throw err;
  console.log(data);
});
Когда Node.js выполняет асинхронную операцию, libuv регистрирует соответствующий запрос. В зависимости от типа операции, libuv либо использует асинхронные механизмы операционной системы (например, epoll в Linux, kqueue в macOS или IOCP в Windows), либо делегирует задачу пулу потоков, если асинхронный API недоступен.

Модульная система и управление памятью



Node.js использует модульную систему для организации кода в многократно используемые компоненты. Изначально Node.js опирался на спецификацию CommonJS, которая определяет способ структурирования и загрузки модулей. В более поздних версиях добавилась поддержка ES модулей, ставших стандартом в современном JavaScript. В CommonJS модули загружаются с помощью функции require(), а экспортируются через объект module.exports:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
// math.js
module.exports = {
  add: function(a, b) {
    return a + b;
  },
  subtract: function(a, b) {
    return a - b;
  }
};
 
// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8
С ES модулями синтаксис стал более декларативным, используя ключевые слова import и export:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
// math.js
export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}
 
// app.js
import { add, subtract } from './math';
console.log(add(5, 3)); // 8
Что касается управления памятью, Node.js полагается на сборщик мусора V8. Однако работа с памятью в серверных приложениях имеет свои особенности. Например, обработка больших объемов данных может приводить к утечкам памяти, если ссылки на объекты сохраняются дольше, чем это необходимо.
Node.js предоставляет инструменты для мониторинга и профилирования использования памяти, что помогает выявлять и устранять проблемы:

JavaScript
1
2
3
4
const memoryUsage = process.memoryUsage();
console.log(memoryUsage);
// Выведет что-то вроде:
// { rss: 26247168, heapTotal: 5767168, heapUsed: 3573032, external: 8772 }
Где rss (Resident Set Size) — это объем памяти, выделенный для процесса Node.js, включая сам код, стек и кучу; heapTotal — общий размер кучи JavaScript; heapUsed — используемая часть кучи; external — память, используемая V8 для объектов, привязанных к JavaScript из C++, таких как буферы.

Взаимодействие JavaScript с низкоуровневыми компонентами



Одна из сильных сторон Node.js — возможность использовать JavaScript для управления низкоуровневыми операциями. Это достигается через набор встроенных модулей, таких как fs для работы с файловой системой, net для сетевого программирования и http для создания веб-серверов.
Когда вы вызываете API Node.js из JavaScript, происходит следующее:
1. Ваш JavaScript-код вызывает функцию Node.js API (например, fs.readFile).
2. Node.js обрабатывает вызов и создает соответствующую задачу для libuv.
3. Libuv планирует выполнение задачи, используя событийный цикл или пул потоков.
4. Когда задача завершается, libuv сигнализирует об этом Node.js.
5. Node.js вызывает соответствующий колбэк в JavaScript.

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

Система модулей: от CommonJS к ES Modules



Хотя базовые принципы модульной системы мы уже затронули, стоит глубже погрузиться в то, как разные системы модулей работают в среде Node.js. CommonJS стал стандартом де-факто для Node.js с самого начала. Его синтаксис уже знаком многим разработчикам:

JavaScript
1
2
3
4
5
6
7
// Импорт модуля
const http = require('http');
 
// Экспорт функциональности
module.exports = function(req, res) {
  res.end('Hello World');
};
Но что происходит, когда вы вызываете require()? Процесс загрузки модуля включает несколько шагов:

1. Резолвинг пути к модулю (поиск файла по указанному пути или в node_modules).
2. Загрузка файла модуля и его обертывание в функцию.
3. Компиляция и выполнение кода модуля.
4. Кэширование результатов для дальнейшего использования.

CommonJS загружает модули синхронно — каждый вызов require() блокирует выполнение, пока модуль не будет загружен полностью. Это не проблема для серверных приложений, но становится неприемлемым в браузерной среде, что послужило толчком к разработке ES Modules.

ES Modules, появившиеся в спецификации ECMAScript 2015 (ES6), предлагают статический импорт, что позволяет анализировать зависимости во время компиляции, а не во время выполнения:

JavaScript
1
2
3
4
5
6
7
8
9
10
// Импорт с деструктуризацией
import { createServer } from 'http';
 
// Импорт по умолчанию
import express from 'express';
 
// Экспорт
export function handler(req, res) {
  res.end('Hello from ES modules');
}
Node.js постепенно внедрял поддержку ES Modules. Начиная с версии 13.2.0, ES Modules перестали считаться экспериментальной функциональностью. Сегодня можно использовать файлы с расширением .mjs или указать "type": "module" в файле package.json для использования ES Modules в проекте.

Хотя ES Modules представляют более современный и гибкий подход, переход всей экосистемы Node.js с CommonJS занимает время, и многие пакеты до сих пор используют CommonJS. К счастью, Node.js обеспечивает совместимость между системами модулей, позволяя импортировать CommonJS модули в ES Modules и наоборот, с некоторыми ограничениями.

Работа сборщика мусора V8 в контексте Node.js



Сборщик мусора (GC) V8 играет критическую роль в управлении памятью приложений Node.js. Понимание его работы помогает писать более производительный код и избегать утечек памяти. V8 использует генерационный подход к сборке мусора, разделяя кучу на несколько поколений:

1. Молодое поколение (Nursery) — новые объекты создаются здесь,
2. Промежуточное поколение (Intermediate) — объекты, пережившие несколько сборок,
3. Старое поколение (Old) — долгоживущие объекты.

Большинство объектов в JavaScript-приложениях живут недолго. Это называется "гипотезой недолговечности" (Generational Hypothesis). Основываясь на этой гипотезе, V8 оптимизирует сборку мусора, фокусируясь прежде всего на молодом поколении, где сборка происходит чаще и быстрее.

JavaScript
1
2
3
4
5
6
// Этот код создаёт много временных объектов, которые быстро становятся мусором
function processLargeArray(arr) {
  return arr.map(x => x * 2)
            .filter(x => x > 10)
            .reduce((acc, x) => acc + x, 0);
}
В Node.js-приложениях, особенно серверах с высокой нагрузкой, важно понимать, как GC влияет на производительность. Когда происходит полная сборка мусора (Major GC), она может приостановить выполнение JavaScript, что потенциально приводит к задержкам в обработке запросов. В современных версиях V8 используются различные оптимизации для минимизации задержек:
  • Параллельная маркировка (Concurrent marking) — маркировка объектов происходит параллельно с выполнением JavaScript.
  • Инкрементальная сборка мусора — процесс разбивается на мелкие шаги для сокращения пауз.
  • Компактификация — перераспределение памяти для уменьшения фрагментации.

Node.js предоставляет флаги командной строки для тонкой настройки GC, что может быть полезно для приложений с особыми требованиями к производительности:

Bash
1
2
# Пример запуска Node.js с настройками GC
node --max-old-space-size=4096 --optimize-for-size app.js

Нативные аддоны и их интеграция с ядром Node.js



Несмотря на мощь JavaScript, иногда необходимо интегрироваться с нативным кодом для достижения максимальной производительности или доступа к низкоуровневым API. Для этого в Node.js существуют нативные аддоны — динамически подключаемые библиотеки, написанные на C/C++. Нативные аддоны могут выполнять несколько важных функций:
  • Обеспечивать высокопроизводительные операции, требующие прямого доступа к CPU/GPU.
  • Предоставлять мост к существующим C/C++ библиотекам.
  • Реализовывать функции, требующие прямого доступа к системным ресурсам.

Классический способ создания нативных аддонов — использование Node.js API (N-API), который предоставляет стабильный ABI (Application Binary Interface):

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// hello.cc
#include <node.h>
 
namespace demo {
 
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
 
void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "world").ToLocalChecked());
}
 
void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}
 
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
 
}  // namespace demo
Создание и компиляция нативных аддонов требует установки компилятора и знания системы сборки node-gyp. Этот процесс гораздо сложнее, чем написание чистого JavaScript, но результаты могут стоить затраченных усилий для критичных к производительности частей приложения. Благодаря N-API, Node.js гарантирует, что нативные аддоны будут работать с разными версиями Node без необходимости перекомпиляции, что раньше было серьезной проблемой.

Работа с бинарными данными и типизированными массивами



Обработка бинарных данных — неотъемлемая часть многих серверных приложений, от потоковой передачи файлов до взаимодействия с базами данных и внешними API. Node.js предоставляет несколько мощных примитивов для работы с бинарными данными. Главный из них — это класс Buffer, который представляет собой последовательность байтов фиксированной длины. Буферы похожи на массивы целых чисел, но соответствуют блокам памяти за пределами стандартной JavaScript-кучи:

JavaScript
1
2
3
4
5
6
7
// Создание буфера
const buf = Buffer.from('Hello World', 'utf8');
console.log(buf.toString('hex')); // 48656c6c6f20576f726c64
 
// Работа с бинарными данными
const binaryData = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
console.log(binaryData.toString()); // "buffer"
Буферы особенно полезны при работе с файлами и сетью. Например, при чтении файла можно указать, что нужно получить результат в виде буфера, а не строки:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs');
 
fs.readFile('image.png', (err, buffer) => {
  if (err) throw err;
  
  // buffer содержит бинарные данные изображения
  console.log(`Размер файла: ${buffer.length} байт`);
  
  // Можно манипулировать битами напрямую
  const magicNumber = buffer.slice(0, 4).toString('hex');
  console.log(`Магические байты: ${magicNumber}`);
});
Помимо буферов, Node.js поддерживает стандартные JavaScript типизированные массивы, такие как Uint8Array и Float64Array, которые предоставляют структурированный доступ к бинарным данным:

JavaScript
1
2
3
4
5
6
7
8
9
// Создание типизированного массива из буфера
const buf = Buffer.from([1, 2, 3, 4]);
const uint32array = new Uint32Array(buf.buffer, buf.byteOffset, buf.length / Uint32Array.BYTES_PER_ELEMENT);
 
// Можно использовать типизированные массивы напрямую
const float64Array = new Float64Array(10);
for (let i = 0; i < float64Array.length; i++) {
  float64Array[i] = Math.random();
}
Интересно, что Buffer в Node.js фактически наследуется от Uint8Array, что обеспечивает совместимость и позволяет использовать все методы типизированных массивов с буферами.
Для обработки крупных массивов бинарных данных без загрузки их целиком в память, Node.js предоставляет мощную абстракцию — потоки (Streams). Они позволяют обрабатывать данные по мере их поступления, что существенно уменьшает потребление памяти:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fs = require('fs');
const crypto = require('crypto');
 
// Вычисление хеша большого файла без загрузки его целиком в память
const hash = crypto.createHash('sha256');
const input = fs.createReadStream('largefile.bin');
 
input.on('data', chunk => {
  hash.update(chunk);
});
 
input.on('end', () => {
  console.log(hash.digest('hex'));
});
Понимание этих компонентов системы Node.js — ключ к написанию эффективных, надежных и безопасных приложений. В следующем разделе мы детально рассмотрим, как функционирует событийный цикл — механизм, который связывает все эти компоненты воедино и обеспечивает асинхронную природу Node.js.

Не могу с решениями задач на node js (я понимаю как их решить на js, но как на node js не знаю)
1) Однажды ковбой Джо решил обзавестись револьвером и пришёл в оружейный магазин. У ковбоя s долларов, а на выбор представлены n револьверов с...

Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'
Привет, есть следующий код который срабатывает правильно, как и задумано (когда создано 10параграфов - удает все), но выдает ошибку в консоль...

Не запускается пакет node js - пакетами? npm? сам node? gulp?
Всем доброго времени суток. Есть такая проблема, пытаюсь перебраться на Linux (Ubuntu) Установил node js по докам (да и вообще как только не...

Выложил приложение Node js на хост, ошибка (node:12900) [DEP0005] DeprecationWarning: Buffer()
Выложил приложение Node js на хост, ошибка (node:12900) DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use...


Событийный цикл подробно



Событийный цикл — сердце асинхронной модели Node.js. Это механизм, который позволяет однопоточному JavaScript обрабатывать тысячи одновременных соединений без блокировки выполнения. Многие разработчики, даже те, кто годами работает с Node.js, не до конца понимают, как функционирует событийный цикл. Давайте погрузимся в его устройство.

Фазы цикла и их назначение



Событийный цикл Node.js не просто бесконечный цикл — он имеет чётко определённую структуру, состоящую из нескольких фаз, выполняемых последовательно:

1. Фаза таймеров (Timers): Выполняет колбэки, запланированные через setTimeout() и setInterval(). Важно понимать, что указанное в этих функциях время — это минимальная задержка, а не гарантированный момент выполнения.

2. Фаза отложенных колбэков (Pending Callbacks): Выполняет колбэки операций ввода-вывода, отложенных до следующей итерации цикла. Типичный пример — некоторые системные операции, такие как TCP ошибки.

3. Фаза подготовки (Idle, Prepare): Используется внутренне Node.js и редко имеет значение для прикладных разработчиков.

4. Фаза опроса (Poll): Ключевая фаза цикла. Здесь происходит:
- Вычисление времени блокировки для ввода-вывода.
- Обработка событий из очереди опроса.
- Выполнение колбэков по мере их появления.
Если очередь пуста, Node.js может ждать здесь новых событий (если нет запланированных таймеров или immediate).

5. Фаза проверки (Check): Выполняет колбэки, зарегистрированные через setImmediate(). Эта фаза выполняется сразу после фазы опроса.

6. Фаза закрытия (Close Callbacks): Выполняет колбэки закрытия, например socket.on('close', ...).

После завершения всех фаз цикл начинается заново. Кроме того, между любыми фазами выполняются микрозадачи (process.nextTick() и промисы).

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Пример, демонстрирующий порядок выполнения фаз
console.log('Начало программы');
 
setTimeout(() => {
  console.log('Таймер');
}, 0);
 
setImmediate(() => {
  console.log('Immediate');
});
 
process.nextTick(() => {
  console.log('NextTick');
});
 
console.log('Конец программы');
 
// Вывод:
// Начало программы
// Конец программы
// NextTick
// Таймер (или Immediate - может варьироваться)
// Immediate (или Таймер - может варьироваться)
Порядок выполнения setTimeout(fn, 0) и setImmediate() может различаться в зависимости от контекста, загруженности системы и других факторов. Однако, внутри колбэков ввода-вывода setImmediate() всегда выполнится раньше таймеров:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');
 
fs.readFile('some-file.txt', () => {
  setTimeout(() => {
    console.log('Таймер внутри I/O колбэка');
  }, 0);
  
  setImmediate(() => {
    console.log('Immediate внутри I/O колбэка');
  });
});
 
// Вывод всегда будет:
// Immediate внутри I/O колбэка
// Таймер внутри I/O колбэка

Микрозадачи и макрозадачи в событийном цикле



В контексте событийного цикла задачи делятся на два типа:

Макрозадачи (Macrotasks) — это обычные асинхронные операции:
  • setTimeout/setInterval,
  • setImmediate,
  • Операции ввода-вывода,
  • Обработчики событий.

Микрозадачи (Microtasks) выполняются сразу после текущей операции, до следующей макрозадачи:
  • process.nextTick() — специфичный для Node.js метод, который имеет наивысший приоритет среди всех асинхронных операций.
  • Обработчики промисов (.then(), .catch(), .finally()).
  • queueMicrotask().

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

JavaScript
1
2
3
4
5
6
Promise.resolve().then(() => console.log('Промис'));
process.nextTick(() => console.log('nextTick'));
 
// Вывод всегда:
// nextTick
// Промис
Это поведение критически важно для понимания потока выполнения асинхронного кода.

Практический анализ очередности выполнения колбэков



Чтобы лучше понять очерёдность выполнения разных типов задач, рассмотрим сложный пример:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
console.log('1. Синхронный код');
 
setTimeout(() => {
  console.log('2. Таймер 1');
  
  process.nextTick(() => {
    console.log('3. nextTick внутри таймера');
  });
  
  Promise.resolve().then(() => {
    console.log('4. Промис внутри таймера');
  });
}, 0);
 
process.nextTick(() => {
  console.log('5. nextTick 1');
  
  setTimeout(() => {
    console.log('6. Вложенный таймер в nextTick');
  }, 0);
});
 
Promise.resolve().then(() => {
  console.log('7. Промис 1');
  
  process.nextTick(() => {
    console.log('8. nextTick внутри промиса');
  });
});
 
setTimeout(() => {
  console.log('9. Таймер 2');
}, 0);
 
setImmediate(() => {
  console.log('10. Immediate');
});
 
console.log('11. Ещё синхронный код');
Результат выполнения:
JavaScript
1
2
3
4
5
6
7
8
9
10
11
1. Синхронный код
11. Ещё синхронный код
5. nextTick 1
7. Промис 1
8. nextTick внутри промиса
2. Таймер 1
3. nextTick внутри таймера
4. Промис внутри таймера
9. Таймер 2
10. Immediate
6. Вложенный таймер в nextTick
Анализ очередности:
1. Сначала выполняется весь синхронный код.
2. Затем обрабатываются все микрозадачи из очереди (nextTick, промисы).
3. Далее идет выполнение фаз событийного цикла (таймеры, ввод-вывод, immediates).
4. После выполнения каждой макрозадачи снова проверяется и очищается очередь микрозадач.

Важно отметить несколько моментов:
  • process.nextTick() имеет приоритет над обработчиками промисов.
  • Внутри каждой фазы колбэки выполняются в порядке FIFO (First-In-First-Out).
  • После каждого выполненного колбэка событийный цикл проверяет очереди микрозадач.
  • Между разными макрозадачами (setTimeout и setImmediate) порядок может варьироваться, если они запланированы из синхронного кода.

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

Оптимизация таймеров и работы с setTimeout/setImmediate



Таймеры — одна из самых часто используемых возможностей Node.js, но они не так просты, как может показаться. Неэффективное использование таймеров может привести к ненужным нагрузкам на событийный цикл и деградации производительности. Одна из ключевых вещей, которую нужно понимать о setTimeout и setInterval: указанное время задержки — это минимальное время до выполнения колбэка, но не гарантированное. Если событийный цикл загружен, колбэк может выполниться значительно позже.

JavaScript
1
2
3
4
5
6
7
// Создание множества таймеров может вызвать проблемы
for (let i = 0; i < 10000; i++) {
  setTimeout(() => {
    // Тяжелая операция
    const result = Array(10000).fill(1).map(x => x * 2).reduce((a, b) => a + b);
  }, 1000);
}
Такой код создаст 10000 таймеров, все они сработают примерно в одно время, что вызовет перегрузку процессора и, возможно, временную блокировку событийного цикла. Вместо этого лучше использовать одиночный таймер с обработкой пакета задач:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const tasks = Array(10000).fill(null).map((_, i) => i);
let completed = 0;
 
setTimeout(() => {
  // Обрабатываем задачи пакетами
  const processBatch = () => {
    const batch = tasks.slice(completed, completed + 500);
    if (batch.length === 0) return;
    
    batch.forEach(taskId => {
      // Выполнение задачи
      const result = Array(10000).fill(1).map(x => x * 2).reduce((a, b) => a + b);
    });
    
    completed += batch.length;
    
    // Отдаём управление событийному циклу, используя setImmediate
    setImmediate(processBatch);
  };
  
  processBatch();
}, 1000);
Использование setImmediate вместо вложенных setTimeout может существенно улучшить производительность для операций, которые должны выполняться как можно скорее, но не блокировать поток выполнения. В отличие от setTimeout(fn, 0), setImmediate гарантирует выполнение в следующей итерации цикла, как только завершится фаза опроса (poll).

Обработка исключений в асинхронном контексте



Одна из особенностей асинхронного программирования в Node.js — сложность отлова исключений. В синхронном коде мы привыкли использовать конструкцию try-catch:

JavaScript
1
2
3
4
5
6
try {
  const result = riskyOperation();
  // Обработка результата
} catch (error) {
  console.error('Ошибка перехвачена:', error);
}
Однако в асинхронном коде этот подход часто не срабатывает:

JavaScript
1
2
3
4
5
6
7
8
try {
  setTimeout(() => {
    throw new Error('Асинхронная ошибка');
  }, 100);
} catch (error) {
  // Этот блок никогда не выполнится
  console.error('Ошибка перехвачена?', error);
}
Когда ошибка возникает в асинхронной функции, стек вызовов, который существовал при планировании операции, уже не существует, когда операция фактически выполняется. Поэтому здесь нужны другие механизмы:

1. Колбэки с проверкой ошибок — традиционный подход в Node.js:

JavaScript
1
2
3
4
5
6
7
fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error('Ошибка чтения файла:', err);
    return;
  }
  // Обработка данных
});
2. Промисы с .catch():

JavaScript
1
2
3
4
5
6
7
fs.promises.readFile('file.txt')
  .then(data => {
    // Обработка данных
  })
  .catch(err => {
    console.error('Ошибка чтения файла:', err);
  });
3. Async/await с try-catch:

JavaScript
1
2
3
4
5
6
7
8
async function readFileContent() {
  try {
    const data = await fs.promises.readFile('file.txt');
    // Обработка данных
  } catch (err) {
    console.error('Ошибка чтения файла:', err);
  }
}
4. Обработка необработанных отклонений промисов:

JavaScript
1
2
3
4
5
process.on('unhandledRejection', (reason, promise) => {
  console.error('Необработанное отклонение промиса:', reason);
  // Можно завершить процесс или выполнить другие действия
  // process.exit(1);
});
5. Обработка необработанных исключений:

JavaScript
1
2
3
4
5
6
process.on('uncaughtException', (err) => {
  console.error('Необработанное исключение:', err);
  // Важно: после необработанного исключения состояние процесса может быть нестабильным
  // Рекомендуется выполнить логирование и перезапустить процесс
  process.exit(1);
});

Стратегии предотвращения блокировки событийного цикла



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

Основные признаки блокировки:
  • Увеличение времени отклика сервера.
  • Сообщения "Event loop blocked for X ms" в логах мониторинга.
  • Нестабильная производительность под нагрузкой.

Рассмотрим несколько стратегий для предотвращения блокировки:

1. Разбиение тяжёлых вычислений на части:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function processLargeArray(array, batchSize = 1000) {
  let index = 0;
 
  function processBatch() {
    const end = Math.min(index + batchSize, array.length);
    
    // Обработка текущего пакета
    for (let i = index; i < end; i++) {
      // Тяжелая операция над элементом
      array[i] = heavyComputationOnItem(array[i]);
    }
    
    index = end;
    
    // Если есть еще элементы, планируем следующий пакет
    if (index < array.length) {
      setImmediate(processBatch);
    } else {
      console.log('Обработка завершена');
    }
  }
  
  // Начинаем обработку
  processBatch();
}
2. Использование таймеров для разделения вычислений:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function calculatePrimes(max) {
  const primes = [];
  let currentNumber = 2;
  
  function checkNextBatch() {
    const endTime = Date.now() + 50; // Максимум 50мс на пакет
    
    while (Date.now() < endTime && currentNumber <= max) {
      let isPrime = true;
      
      for (let i = 2; i <= Math.sqrt(currentNumber); i++) {
        if (currentNumber % i === 0) {
          isPrime = false;
          break;
        }
      }
      
      if (isPrime) primes.push(currentNumber);
      currentNumber++;
    }
    
    if (currentNumber <= max) {
      // Еще не закончили, планируем следующий пакет
      setTimeout(checkNextBatch, 0);
    } else {
      console.log(`Найдено ${primes.length} простых чисел`);
    }
  }
  
  checkNextBatch();
}
3. Отслеживание задержек событийного цикла:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
let lastCheck = Date.now();
 
setInterval(() => {
  const now = Date.now();
  const delay = now - lastCheck - 1000; // Ожидаемая задержка 1000мс
  
  if (delay > 100) { // Задержка более 100мс считается проблемной
    console.warn(`Событийный цикл был заблокирован на ${delay}мс`);
    // Можно отправить метрику в систему мониторинга
  }
  
  lastCheck = now;
}, 1000);

Работа с многопоточностью через Worker Threads API



Несмотря на однопоточную природу основного процесса Node.js, с версии 10.5.0 доступен Worker Threads API, который позволяет запускать JavaScript в отдельных потоках. Это особенно полезно для ресурсоёмких вычислений, которые могут блокировать основной поток. Создание и использование работника (worker):

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
 
if (isMainThread) {
  // Это основной поток
  const worker = new Worker(__filename, {
    workerData: { input: Array(1000000).fill(1) }
  });
  
  worker.on('message', result => {
    console.log('Результат от работника:', result);
  });
  
  worker.on('error', err => {
    console.error('Ошибка работника:', err);
  });
  
  worker.on('exit', code => {
    if (code !== 0)
      console.error(`Работник завершился с кодом ${code}`);
  });
} else {
  // Это работник
  const { input } = workerData;
  
  // Выполняем тяжелую операцию без блокировки основного потока
  const result = input.map(x => x * 2).reduce((a, b) => a + b, 0);
  
  // Отправляем результат основному потоку
  parentPort.postMessage(result);
}
Worker Threads предлагают несколько преимуществ перед другими формами параллелизма в Node.js, такими как child_process:
  • Они используют меньше ресурсов, чем отдельные процессы.
  • Они могут делить память с помощью SharedArrayBuffer.
  • Они могут обмениваться данными через MessagePort.

Пример использования SharedArrayBuffer:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { Worker, isMainThread } = require('worker_threads');
 
if (isMainThread) {
  // Создаем SharedArrayBuffer, доступный между потоками
  const buffer = new SharedArrayBuffer(4);
  const view = new Int32Array(buffer);
  view[0] = 0;
  
  const worker = new Worker(__filename, { workerData: { buffer } });
  
  // Запускаем таймер для чтения обновлений
  setInterval(() => {
    console.log('Текущее значение:', Atomics.load(view, 0));
  }, 100);
} else {
  const { buffer } = workerData;
  const view = new Int32Array(buffer);
  
  // Инкрементируем значение каждые 500мс
  setInterval(() => {
    Atomics.add(view, 0, 1);
  }, 500);
}
Worker Threads особенно полезны для:
  • Сложных математических вычислений.
  • Обработки больших массивов данных.
  • Работы с криптографией.
  • Обработки изображений и видео.
  • Других CPU-интенсивных задач.

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

Производительность на практике: особенности тонкой настройки



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

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Профилирование CPU
const profiler = require('v8-profiler-next');
const fs = require('fs');
 
// Начинаем профилирование
profiler.startProfiling('CPU Profile');
 
// Через некоторое время останавливаем и сохраняем результат
setTimeout(() => {
  const profile = profiler.stopProfiling();
  profile.export()
    .pipe(fs.createWriteStream('./cpuprofile.cpuprofile'))
    .on('finish', () => profile.delete());
}, 30000);
Утечки памяти - ещё одна распространённая проблема. Они могут возникать из-за замыканий, неосвобождённых обработчиков событий или неправильной работы с кешем. Простой пример утечки памяти:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
const cache = new Map();
 
function badCaching(key, value) {
  // Отсутствует механизм очистки кеша
  cache.set(key, value);
  
  // Кеш будет расти бесконечно
  setInterval(() => {
    console.log(cache.get(key));
  }, 1000);
}
Исправленная версия с механизмом очистки:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LRUCache {
  constructor(maxSize = 1000) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }
 
  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
 
  get(key) {
    const value = this.cache.get(key);
    if (value) {
      // Обновляем позицию элемента
      this.cache.delete(key);
      this.cache.set(key, value);
    }
    return value;
  }
}
Параллельная обработка данных может значительно улучшить производительность. Worker Threads API предоставляет удобный способ распределения нагрузки:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const os = require('os');
 
function processDataParallel(data, processingFn) {
  return new Promise((resolve, reject) => {
    const numCPUs = os.cpus().length;
    const chunkSize = Math.ceil(data.length / numCPUs);
    const workers = [];
    let completedWorkers = 0;
    const results = [];
 
    for (let i = 0; i < numCPUs; i++) {
      const start = i * chunkSize;
      const end = Math.min(start + chunkSize, data.length);
      
      const worker = new Worker(`
        const { parentPort, workerData } = require('worker_threads');
        const { data, start, end, fnStr } = workerData;
        const processingFn = eval('(' + fnStr + ')');
        
        const result = data.slice(start, end).map(processingFn);
        parentPort.postMessage(result);
      `, {
        eval: true,
        workerData: {
          data,
          start,
          end,
          fnStr: processingFn.toString()
        }
      });
 
      worker.on('message', (result) => {
        results[i] = result;
        completedWorkers++;
        
        if (completedWorkers === numCPUs) {
          resolve([].concat(...results));
        }
      });
 
      worker.on('error', reject);
      workers.push(worker);
    }
  });
}
Оптимизация работы с базами данных играет критическую роль в производительности веб-приложений. Пулы соединений и правильная индексация могут значительно ускорить работу:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const { Pool } = require('pg');
 
const pool = new Pool({
  user: 'dbuser',
  host: 'database.server.com',
  database: 'mydb',
  password: 'secretpassword',
  port: 5432,
  // Оптимальные настройки пула
  max: 20, // максимум соединений
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});
 
// Переиспользование соединений
async function executeQueries(queries) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const results = [];
    
    for (const query of queries) {
      const result = await client.query(query);
      results.push(result);
    }
    
    await client.query('COMMIT');
    return results;
  } catch (e) {
    await client.query('ROLLBACK');
    throw e;
  } finally {
    client.release();
  }
}
Кэширование и мемоизация - мощные инструменты оптимизации. Реализация мемоизации с ограничением по времени:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function memoizeWithTTL(fn, ttl = 60000) {
  const cache = new Map();
  
  return function (...args) {
    const key = JSON.stringify(args);
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.value;
    }
    
    const result = fn.apply(this, args);
    cache.set(key, {
      value: result,
      timestamp: Date.now()
    });
    
    return result;
  };
}
 
// Пример использования
const expensiveOperation = memoizeWithTTL((n) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n * n);
    }, 1000);
  });
}, 5000); // кеш на 5 секунд
Работа с потоками данных (streams) позволяет эффективно обрабатывать большие объёмы данных без загрузки их целиком в память. Пример трансформации CSV-файла:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fs = require('fs');
const { Transform } = require('stream');
const csv = require('csv-parser');
 
const transformStream = new Transform({
  objectMode: true,
  transform(chunk, encoding, callback) {
    // Преобразование данных
    const transformed = {
      name: chunk.name.toUpperCase(),
      age: parseInt(chunk.age) + 1,
      city: chunk.city
    };
    
    callback(null, JSON.stringify(transformed) + '\n');
  }
});
 
fs.createReadStream('input.csv')
  .pipe(csv())
  .pipe(transformStream)
  .pipe(fs.createWriteStream('output.json'))
  .on('finish', () => console.log('Преобразование завершено'));
В целом, оптимизация производительности Node.js приложений требует комплексного подхода и глубокого понимания внутренних механизмов работы платформы. Важно помнить, что преждевременная оптимизация может усложнить код без существенного выигрыша в производительности. Следует начинать с профилирования и выявления реальных узких мест, а затем применять соответствующие методы оптимизации.

Применение Node.js в разных типах приложений



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

Сильные стороны Node.js



REST API и микросервисы - естественная среда для Node.js. Возможность быстро обрабатывать множество параллельных запросов и встроенная поддержка JSON делают платформу идеальной для создания API:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const express = require('express');
const app = express();
 
class UserService {
constructor() {
  this.users = new Map();
}
 
async createUser(userData) {
  const userId = Math.random().toString(36).substr(2, 9);
  this.users.set(userId, userData);
  return { id: userId, ...userData };
}
 
async getUser(userId) {
  const user = this.users.get(userId);
  if (!user) throw new Error('User not found');
  return user;
}
}
 
const userService = new UserService();
 
app.use(express.json());
 
app.post('/users', async (req, res) => {
try {
  const user = await userService.createUser(req.body);
  res.status(201).json(user);
} catch (err) {
  res.status(400).json({ error: err.message });
}
});
 
app.get('/users/:id', async (req, res) => {
try {
  const user = await userService.getUser(req.params.id);
  res.json(user);
} catch (err) {
  res.status(404).json({ error: err.message });
}
});
 
app.listen(3000);
Потоковая обработка данных также является сильной стороной Node.js. Встроенная поддержка потоков позволяет обрабатывать большие объёмы данных с минимальным потреблением памяти:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const { Transform } = require('stream');
const fs = require('fs');
 
class DataTransformer extends Transform {
constructor(options = {}) {
  super({ ...options, objectMode: true });
  this.batchSize = options.batchSize || 1000;
  this.batch = [];
}
 
_transform(chunk, encoding, callback) {
  this.batch.push(chunk);
  
  if (this.batch.length >= this.batchSize) {
    this.processBatch();
  }
  
  callback();
}
 
_flush(callback) {
  if (this.batch.length > 0) {
    this.processBatch();
  }
  callback();
}
 
processBatch() {
  // Обработка пакета данных
  const result = this.batch.map(item => ({
    ...item,
    processed: true,
    timestamp: Date.now()
  }));
  
  this.push(result);
  this.batch = [];
}
}
 
// Использование трансформера
fs.createReadStream('input.json')
.pipe(new DataTransformer({ batchSize: 100 }))
.pipe(fs.createWriteStream('output.json'));

Ограничения и компромисы



CPU-интенсивные операции - не самая сильная сторона Node.js. Для таких задач лучше использовать языки, оптимизированные для вычислений, такие как Rust или Go. Однако существуют способы обойти это ограничение:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const { Worker } = require('worker_threads');
const os = require('os');
 
class ComputationPool {
constructor(workerScript, numWorkers = os.cpus().length) {
  this.workers = new Array(numWorkers).fill(null).map(() => 
    new Worker(workerScript)
  );
  this.nextWorker = 0;
}
 
async compute(data) {
  const worker = this.workers[this.nextWorker];
  this.nextWorker = (this.nextWorker + 1) % this.workers.length;
  
  return new Promise((resolve, reject) => {
    worker.once('message', resolve);
    worker.once('error', reject);
    worker.postMessage(data);
  });
}
 
terminate() {
  return Promise.all(
    this.workers.map(worker => worker.terminate())
  );
}
}
 
// Использование пула для тяжёлых вычислений
const pool = new ComputationPool('./compute-worker.js');
const results = await Promise.all(
tasks.map(task => pool.compute(task))
);
Работа с базами данных требует особого внимания в Node.js. Асинхронная природа платформы может создавать неожиданные проблемы при управлении транзакциями:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class TransactionManager {
constructor(pool) {
  this.pool = pool;
}
 
async withTransaction(callback) {
  const client = await this.pool.connect();
  
  try {
    await client.query('BEGIN');
    const result = await callback(client);
    await client.query('COMMIT');
    return result;
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}
}
 
// Использование менеджера транзакций
const tm = new TransactionManager(pool);
await tm.withTransaction(async (client) => {
const { rows } = await client.query(
  'UPDATE accounts SET balance = balance - $1 WHERE id = $2 RETURNING *',
  [amount, fromId]
);
 
if (rows[0].balance < 0) {
  throw new Error('Insufficient funds');
}
 
await client.query(
  'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
  [amount, toId]
);
});
Масштабирование Node.js приложений имеет свои особенности. PM2 или Docker упрощают управление несколькими экземплярами приложения:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ecosystem.config.js для PM2
module.exports = {
apps: [{
  name: 'api-server',
  script: './server.js',
  instances: 'max',
  exec_mode: 'cluster',
  env: {
    NODE_ENV: 'production',
    PORT: 3000
  },
  env_production: {
    NODE_ENV: 'production',
    PORT: 80
  }
}]
};
Выбор Node.js для проекта должен основываться на тщательном анализе требований. При правильном применении и понимании ограничений платформы, Node.js может стать надёжным фундаментом для широкого спектра приложений - от небольших утилит до крупных распределённых систем.

Async await изнутри - как устроено?
const sleep = sec =&gt; new Promise(resolve =&gt; setTimeout(resolve, sec)); (async() =&gt; { setTimeout(() =&gt; console.log('1000'), 1000); ...

JQuery изнутри
вечер добрый, кто подскажет не просвещенному, один момент из работы jQuery? var div = $('div'); console.log(div) // result результат...

Почему изнутри функции я не имею доступа к переменной?
You can find HTML at smtpromocodes.com let left = document.getElementById(&quot;button1&quot;); let right = document.getElementById(&quot;button3&quot;); ...

Как работает Node.js
Всем привет. Вопрос простой - принцып работы node. Я так понимаю - если JS интерпритируемый язык - то значит обходится без компиляции. Node получает...

Не работает чат node.js
&lt;!doctype html&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;Socket.IO chat&lt;/title&gt; &lt;style&gt; * { margin: 0; padding: 0; box-sizing: border-box; }...

Node не работает возврат из функции
var titles = GetTitles(); ///.... function GetTitles() { connection.query('SELECT * FROM titles', function (err, rows) { return...

Не работает css на сервере node.js
Создал болванку сайта (связка html + css) и на локальном сервер все работает отлично, а вот когда загружаю в node var express =...

Node v7.9 async/await не работает. Что не так?
Всем привет. Использую node 7.9 и express. Вот код как пример: function mysql_execute(sql, props) { return new Promise(function...

Какова ситуация с import/export в node.js ? (у меня установлена 7.10, но программа в в WS работает)
//режим ES6 включен, webstorm import/export не подчёркивает, версия node 7.10 //в сборках видел применение import/export , но там был babel. Но...

Node js не работает на хостинге
Всем доброго времени суток. Возникла проблема с запуском node js серверной части на удаленном хостинге. Итак. Вчера оформил машину на...

Не работает роутинг Node.js
Помогите, пожалуйста. Пишу простое приложение на Node.js + Angular 5. В server.ts пишу app.get('/login', (req, res) =&gt; { ...

Не работает node-inspector
Добрый день. Почему-то не получается протестировать отладчик node-inspector. Может он тоже устарел и есть модуль посвежее? Поставил npm i -g...

Метки async, javascript, node.js, v8
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Как генерируется мир в Minecraft
GameUnited 28.05.2025
Задумывались ли вы когда-нибудь о том, сколько песчинок на нашей планете? По приблизительным подсчетам - более 7 квинтиллионов! Это цыфра с 18 нулями. И все же, это даже не половина количества. . .
Один суперкластер Kubernetes для вообще всего
Mr. Docker 28.05.2025
Ваша компания развивается, количество сервисов множится, команды разработки разрастаются, а DevOps-инженеры начинают напоминать ту самую собаку из мема про "всё нормально, когда ничего не нормально". . . .
CAP-теорема или почему идеальной распределенной системы не существует
ArchitectMsa 28.05.2025
Вы переводите деньги со своего счета на счет друга. Казалось бы, что может быть проще? Вы открываете приложение банка, вводите сумму, жмете кнопку - и деньги мгновенно переходят с одного счета на. . .
Пишем первый чатбот на C# с нейросетью и Microsoft Bot Framework
UnmanagedCoder 28.05.2025
Microsoft Bot Framework представляет собой мощнейший инструментарий для создания разговорных интерфейсов любой сложности. Он предлагает целостную экосистему, которая включает SDK для C#, сервисы. . .
Event-Driven приложения с Apache Kafka и KafkaFlow в .NET
stackOverflow 26.05.2025
Для . NET разработчиков работа с Kafka традиционно сопряжена с определенными трудностями. Официальный клиент Confluent хорош, но часто требует написания большого количества шаблонного кода. Многие. . .
Квантовое программирование: Реализуем первый алгоритм на Q#
EggHead 26.05.2025
Квантовое программирование — одна из тех областей, которая ещё недавно казалась чем-то недоступным обычному разработчику. Многие представляют себе учёных в белых халатах, работающих с огромными. . .
Запилил скелет проекта физического симулятора.
Hrethgir 26.05.2025
Нзвание публикации "Вычислить VS запомнить — простой и экономичный пример организации обработки потока данных для физической симуляции". Пока только скелет, но всё - будет. . . .
Авто-векторизация в C с GCC 14
NullReferenced 25.05.2025
Современные процессоры давно перестали наращивать тактовую частоту как основной способ увеличения производительности. Вместо этого они обзавелись специализироваными блоками SIMD (Single Instruction,. . .
Типы данных в Python
py-thonny 25.05.2025
Когда я только начинал работать с Python, меня поразило, насколько органично типы данных встроены в синтаксис. Забавно, но факт: некоторые программисты, перешедшие с Java или C++, сначало даже не. . .
.NET Aspire и cloud-native приложения C#
stackOverflow 24.05.2025
. NET Aspire — новый продукт в линейке Microsoft, который вызвал настоящий ажиотаж среди разработчиков облачных приложений. Компания называет его "опинионированным, облачно-ориентированным стеком для. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru
OSZAR »