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

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

Запись от run.dev размещена 29.03.2025 в 18:46
Показов 6534 Комментарии 0

Нажмите на изображение для увеличения
Название: 127c1868-d473-4b0a-a3d6-03b8ef516f55.jpg
Просмотров: 329
Размер:	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...

Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Рефакторинг программы уравнивания.
Massaraksh7 26.05.2026
Пример по предыдущей записи в блоге. Но, надо заметить, что, во-первых, там оптимизация не только математики, но и работы с базой данных, и с графами, а во-вторых, это ещё не всё.
Использование TThread в Lazarus для математических вычислений.
Massaraksh7 25.05.2026
Производя рефакторинг своих программ на предмет ускорения их работы, обратил внимание на такой аспект, как сокращение времени матвычислений. Дело в том, что приходится работать с большими матрицами. . .
Модель здравосохранения 18. Чем здоровее работник, тем быстрее выгорает
anaschu 24.05.2026
Имитационная модель корпоративного здравоохранения: что показывает математика Сегодня в модели рабочего коллектива на AnyLogic появились три новые механики — выгорание через накопленную усталость,. . .
Модель здравосохранения 17. Планы на выгорание
anaschu 23.05.2026
Вот конкретная схема реализации: В классе Работник добавить: накопленнаяУсталость — растёт каждый час работы, снижается в перерывы и болезни коэффициентПрезентеизма — снижает продуктивность. . .
Изменение цветов в палитре gif файла aka фавикона
russiannick 23.05.2026
Изменение цветов в палитре gif файла, юзаемого как фавиконка в составе html-файла, помещенная в base64, средствами нативного Java Script, навеянное сном в майский день. Для работы необходим браузер,. . .
Модель здравосохранения 16. Слишком хорошие и здоровые сотрудники уходят, недовольные зарплатой
anaschu 23.05.2026
Отладка увольнений и настройка производительности Сегодня во второй половине дня разобрались с механикой увольнений и настроили коэффициент сложности заданий. Вот что было сделано. . . .
Как я стал коммунистом))) Модель сохранения здоровья сотрудников, запись блога номер 15
anaschu 23.05.2026
Внезапно хорошее здоровье сотрудников не нужно капиталистам?))
Модель здравоСохранения 15. Как мы чинили AnyLogic модель рабочего коллектива: сочленение диаграммы состояний болезней и поломок в ресурспул
anaschu 23.05.2026
Как мы чинили AnyLogic модель рабочего коллектива Сегодня разобрались с пятью багами, из-за которых модель либо падала с ошибкой, либо давала совершенно бессмысленные результаты. Каждый баг был. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru