Мне нравится записывать полезные вещи об интерфейсах, свойствах, методах, функциях, типах данных, и обо всём прочем, что относится к веб-разработке. Так я заполняю пробелы в знаниях. Сейчас я занят документацией к Node.js, а до этого проработал материалы по HTML, DOM, по Web API, CSS, SVG и EcmaScript.
Чтение документации Node.js открыло мне немало замечательных вещей, о которых я раньше не знал. Ими я хочу поделиться в этом небольшом материале. Начну с самого интересного. Так же я обычно делаю, когда показываю новому знакомому свои гаджеты.
1. Модуль querystring как универсальный парсер
Скажем, вы получили данные из какой-нибудь эксцентричной БД, которая выдала массив пар ключ/значение в примерно таком виде:
name:Sophie;shape:fox;condition:new
. Вполне естественно полагать, что подобное можно легко преобразовать в объект JavaScript. Поэтому вы создаёте пустой объект, затем – массив, разделив строку по символу «;
». Дальше – проходитесь в цикле по каждому элементу этого массива, опять разбиваете строки, теперь уже по символу «:
». В итоге, первый полученный из каждой строки элемент становится именем свойства нового объекта, второй – значением.Всё правильно?
Нет, не правильно. В подобной ситуации достаточно воспользоваться
querystring
.const weirdoString = `name:Sophie;shape:fox;condition:new`;
const result = querystring.parse(weirdoString, `;`, `:`);
// результат:
// {
// name: `Sophie`,
// shape: `fox`,
// condition: `new`,
// };
2. Отладка: V8 Inspector
Если запустить Node с ключом
--inspect
, он сообщит URL. Перейдите по этому адресу в Chrome. А теперь – приятная неожиданность. Нам доступна отладка Node.js с помощью инструментов разработчика Chrome. Настали счастливые времена. Вот руководство на эту тему от Пола Айриша.Надо отметить, что данная функция всё ещё носит статус экспериментальной, но я ей с удовольствием пользуюсь и до сих пор она меня не подводила.
3. Разница между nextTick и setImmediate
Как и в случае со многими другими программными механизмами, запомнить разницу между этими двумя функциями очень просто, если дать им более осмысленные имена.
Итак, функция
process.nextTick()
должна называться process.sendThisToTheStartOfTheQueue()
. А setImmediate()
- sendThisToTheEndOfTheQueue()
.Кстати, вот полезный материал об оптимизации
nextTick
начиная с Node v0.10.0. Маленькое отступление. Я всегда думал, что в React props
должно называться stuffThatShouldStayTheSameIfTheUserRefreshes
, а state
– stuffThatShouldBeForgottenIfTheUserRefreshes
. То, что у этих названий одинаковая длина, считайте удачным совпадением.4. Server.listen принимает объект с параметрами
Я приверженец передачи параметров в виде объекта, например, с именем «options», а не подхода, когда на входе в функцию ожидается куча параметров, которые, к тому же, не имеют имён, да ещё и должны быть расположены в строго определённом порядке. Как оказалось, при настройке сервера на прослушивание запросов можно использовать объект с параметрами.
require(`http`)
.createServer()
.listen({
port: 8080,
host: `localhost`,
})
.on(`request`, (req, res) => {
res.end(`Hello World!`);
});
Эта полезная возможность неплохо спряталась. В документации по
http.Server
о ней – ни слова. Однако, её можно найти в описании net.Server
, наследником которого является http.Server
.5. Относительные пути к файлам
Путь в файловой системе, который передают модулю
fs
, может быть относительным. Точка отсчёта – текущая рабочая директория, возвращаемая process.cwd()
. Вероятно, это и так все знают, но вот я всегда думал, что без полных путей не обойтись.const fs = require(`fs`);
const path = require(`path`);
// почему я всегда делал так...
fs.readFile(path.join(__dirname, `myFile.txt`), (err, data) => {
// делаем что-нибудь полезное
});
// когда мог просто поступить так?
fs.readFile(`./path/to/myFile.txt`, (err, data) => {
// делаем что-нибудь полезное
});
6. Разбор путей к файлам
Обычно, когда мне нужно было вытащить из пути к файлу его имя и расширение, я пользовался регулярными выражениями. Теперь понимаю, что в этом нет совершенно никакой необходимости. То же самое можно сделать стандартными средствами.
myFilePath = `/someDir/someFile.json`;
path.parse(myFilePath).base === `someFile.json`; // true
path.parse(myFilePath).name === `someFile`; // true
path.parse(myFilePath).ext === `.json`; // true
7. Раскраска логов в консоли
Сделаю вид, будто я не знал, что конструкция
console.dir(obj, {colors: true})
позволяет выводить в консоль объекты со свойствами и значениями, выделенными цветом. Это упрощает чтение логов.8. Управление setInterval()
Например, вы используете
setInterval()
для того, чтобы раз в день проводить очистку базы данных. По умолчанию цикл событий Node не остановится до тех пор, пока имеется код, исполнение которого запланировано с помощью setInterval()
. Если вы хотите дать Node отдохнуть (не знаю, на самом деле, какие плюсы можно от этого получить), воспользуйтесь функцией unref()
.const dailyCleanup = setInterval(() => {
cleanup();
}, 1000 * 60 * 60 * 24);
dailyCleanup.unref();
Однако, тут стоит проявить осторожность. Если Node больше ничем не занят (скажем, нет http-сервера, ожидающего подключений), он завершит работу.
9. Константы сигнала завершения процесса
Если вам нравится убивать, то вы, наверняка, уже так делали:
process.kill(process.pid, `SIGTERM`);
Ничего плохого об этой конструкции сказать не могу. Но что, если в команду вкралась ошибка, вызванная опечаткой? В истории программирования известны такие случаи. Второй параметр здесь должен быть строкой или соответствующим целым числом, поэтому тут немудрено написать что-нибудь не то. Для того, чтобы застраховаться от ошибок, можно поступить так:
process.kill(process.pid, os.constants.signals.SIGTERM);
10. Проверка IP-адресов
В Node.js имеется встроенное средство для проверки IP-адресов. Раньше я не раз писал регулярные выражения для того, чтобы это сделать. На большее ума не хватило. Вот как это сделать правильно:
require(`net`).isIP(`10.0.0.1`)
вернёт
4
.require(`net`).isIP(`cats`)
вернёт
0
.Всё верно, коты – это не IP-адреса.
Возможно вы заметили, что в примерах я использую для строк одинарные кавычки. Мне так делать нравится, но я подозреваю, что выглядит это странно, поэтому считаю нужным об этом упомянуть, хотя и сам толком не знаю – зачем. В общем – это мой стиль.
11. Символ конца строки, os.EOL
Вы когда-нибудь задавали в коде символ конца строки? Да? Всё, тушите свет. Вот, специально для тех, кто так делал, замечательная штука:
os.EOL
. В Windows это даст \r\n
, во всех остальных ОС — \n
. Переход на os.EOL
позволит обеспечить единообразное поведение кода в разных операционных системах.Тут я сделаю поправку, так как в момент написания материала недостаточно в эту тему углубился. Читатели предыдущей версии этого поста указали мне на то, что использование
os.EOL
может приводить к неприятностям. Дело в том, что здесь нужно исходить из предположения, что в некоем файле может использоваться или CRLF(\r\n
), или LF (\n
), но полностью быть уверенным в подобном предположении нельзя.Если у вас имеется проект с открытым исходным кодом, и вы хотите принудительно использовать определённый вариант перевода строки, вот правило eslint, которое, отчасти, может в этом помочь. Правда, оно бесполезно, если с текстами поработает Git.
И, всё же,
os.EOL
– не бесполезная игрушка. Например, эта штука может оказаться кстати при формировании лог-файлов, которые не планируется переносить в другие ОС. В подобном случае os.EOL
обеспечивает правильность отображения таких файлов, скажем, для просмотра которых используется Блокнот в Windows Server.const fs = require(`fs`);
// жёстко заданный признак конца строки CRLF
fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
data.split(`\r\n`).forEach(line => {
// делаем что-нибудь полезное
});
});
// признак конца строки зависит от ОС
const os = require(`os`);
fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
data.split(os.EOL).forEach(line => {
// делаем что-нибудь полезное
});
});
12. Коды состояния HTTP
В Node имеется «справочник» с кодами состояния HTTP и их названиями. Я говорю об объекте
http.STATUS_CODES
. Его ключи – это коды состояний, а значения – их названия.Объект http.STATUS_CODES
Вот как этим пользоваться:
someResponse.code === 301; // true
require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true
13. Предотвращение ненужных остановок сервера
Мне всегда казалось малость странным то, что код, похожий на приведённый ниже, приводит к остановке сервера.
const jsonData = getDataFromSomeApi(); // Только не это! Нехорошие данные!
const data = JSON.parse(jsonData); // Громкий стук падающего сервера.
Для того, чтобы предотвратить подобные глупости, прямо в начале приложения для Node.js можно поместить такую конструкцию, выводящую необработанные исключения в консоль:
process.on(`uncaughtException`, console.error);
Я, конечно, нахожусь в здравом уме, поэтому пользуюсь PM2 и оборачиваю всё, что можно, в блоки
try…catch
, когда программирую на заказ, но вот в домашних проектах…Хочу обратить особое внимание на то, что такой подход никоим образом не относится к «лучшим практическим методам разработки», и его использование в больших и сложных приложениях, вероятно, идея плохая. Решайте сами, доверять ли посту в блоге, написанному каким-то чуваком, или официальной документации.
14. Пара слов об once()
В дополнение к методу
on()
, у объектов EventEmitter
имеется и метод once()
. Я совершенно уверен, что я – последний человек на Земле, который об этом узнал. Поэтому ограничусь простым примером, который все и так поймут.server.once(`request`, (req, res) => res.end(`No more from me.`));
15. Настраиваемая консоль
Консоль можно настроить с помощью нижеприведённой конструкции, передавая ей собственные потоки вывода:
new console.Console(standardOut, errorOut)
Зачем? Не знаю точно. Может, вы захотите создать консоль, которая выводит данные в файл, или в сокет, или ещё куда-нибудь.
16. DNS-запросы
Мне тут одна птичка насвистела, что Node не кэширует результаты запросов к DNS. Поэтому, если вы несколько раз обращаетесь к некоему URL, на запросы, без которых можно было бы обойтись, тратятся бесценные миллисекунды. В подобном случае можно выполнить запрос к DNS самостоятельно, с помощью
dns.lookup()
, и закэшировать результаты. Или – воспользоваться пакетом dnscache, который делает то же самое.dns.lookup(`www.myApi.com`, 4, (err, address) => {
cacheThisForLater(address);
});
17. Модуль fs: минное поле
Если ваш стиль программирования похож на мой, то есть, это что-то вроде: «прочту по диагонали кусок документации и буду возиться с кодом, пока он не заработает», тогда вы не застрахованы от проблем с
модулем fs
. Разработчики выполнили огромную работу, направленную на унификацию взаимодействия Node с различными ОС, но их возможности не безграничны. В результате, особенности различных операционных систем разрывают гладь океана кода как острые рифы, которые ещё и заминированы. А вы в этой драме играете роль лодки, которая может на один из рифов сесть.К несчастью, различия, имеющие отношение к
fs
, не сводятся к привычному: «Windows и все остальные», поэтому мы не можем просто отмахнуться, прикрывшись идеей: «да кто пользуется Windows». (Я сначала написал тут целую речь об анти-Windows настроениях в веб-разработке, но в итоге решил это убрать, а то у меня самого глаза на лоб полезли от этой моей проповеди).Вот, вкратце, то, что я обнаружил в документации к модулю
fs
. Уверен, кого-нибудь эти откровения могут клюнуть не хуже жареного петуха.- Свойство
mode
объекта, возвращаемогоfs.stats(),
различается в Windows и в других ОС. В Windows оно может не соответствовать константам режима доступа к файлам, таким, какfs.constants.S_IRWXU
.
- Функция
fs.lchmod()
доступна только в macOS.
- Вызов
fs.symlink()
с параметромtype
поддерживается только в Windows.
- Опция
recursive
, которую можно передать функцииfs.watch()
, работает только на Windows и macOS.
- Функция обратного вызова
fs.watch()
принимает имя файла только в Linux и Windows.
- Вызов
fs.open()
с флагомa+
для директории будет работать во FreeBSD и в Windows, но не сработает в macOS и Linux.
- Параметр
position
, переданныйfs.write()
, будет проигнорирован в Linux в том случае, если файл открыт в режиме присоединения. Ядро игнорирует позицию и добавляет данные к концу файла.
(Я тут не отстаю от моды, называю ОС от Apple «macOS», хотя ещё и двух месяцев не прошло после того, как старое название, OS X, отошло в мир иной).
18. Модуль net вдвое быстрее модуля http
Читая документацию к Node.js, я понял, что модуль
net
– это вещь. Он лежит в основе модуля http
. Это заставило меня задуматься о том, что если нужно организовать взаимодействие серверов (как оказалось, мне это понадобилось), стоит ли использовать исключительно модуль net
?Те, кто плотно занимается сетевым взаимодействием систем, могут и не поверить, что подобный вопрос вообще надо задавать, но я – веб-разработчик, который вдруг свалился в мир серверов и знает только HTTP и ничего больше. Все эти TCP, сокеты, вся эта болтовня о потоках… Для меня это как японский рэп. То есть, мне вроде бы и непонятно, но звучит интригующе.
Для того, чтобы во всём разобраться, поэкспериментировать с
net
и http
, и сравнить их, я настроил пару серверов (надеюсь, вы сейчас слушаете японский рэп) и нагрузил их запросами. В результате http.Server
смог обработать примерно 3400 запросов в секунду, а net.Server
– примерно 5500. К тому же, net.Server
проще устроен.Вот, если интересно, код клиентов и серверов, с которым я экспериментировал. Если не интересно – примите извинения за то, что вам придётся так долго прокручивать страницу.
Вот код client.js.
// Здесь создаются два подключения. Одно – к TCP-серверу, другое – к HTTP (оба описаны в файле server.js).
// Клиенты выполняют множество запросов к серверам и подсчитывают ответы.
// И тот и другой работают со строками.
const net = require(`net`);
const http = require(`http`);
function parseIncomingMessage(res) {
return new Promise((resolve) => {
let data = ``;
res.on(`data`, (chunk) => {
data += chunk;
});
res.on(`end`, () => resolve(data));
});
}
const testLimit = 5000;
/* ------------------ */
/* -- NET client -- */
/* ------------------ */
function testNetClient() {
const netTest = {
startTime: process.hrtime(),
responseCount: 0,
testCount: 0,
payloadData: {
type: `millipede`,
feet: 100,
test: 0,
},
};
function handleSocketConnect() {
netTest.payloadData.test++;
netTest.payloadData.feet++;
const payload = JSON.stringify(netTest.payloadData);
this.end(payload, `utf8`);
}
function handleSocketData() {
netTest.responseCount++;
if (netTest.responseCount === testLimit) {
const hrDiff = process.hrtime(netTest.startTime);
const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6;
const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString();
console.info(`net.Server handled an average of ${requestsPerSecond} requests per second.`);
}
}
while (netTest.testCount < testLimit) {
netTest.testCount++;
const socket = net.connect(8888, handleSocketConnect);
socket.on(`data`, handleSocketData);
}
}
/* ------------------- */
/* -- HTTP client -- */
/* ------------------- */
function testHttpClient() {
const httpTest = {
startTime: process.hrtime(),
responseCount: 0,
testCount: 0,
};
const payloadData = {
type: `centipede`,
feet: 100,
test: 0,
};
const options = {
hostname: `localhost`,
port: 8080,
method: `POST`,
headers: {
'Content-Type': `application/x-www-form-urlencoded`,
},
};
function handleResponse(res) {
parseIncomingMessage(res).then(() => {
httpTest.responseCount++;
if (httpTest.responseCount === testLimit) {
const hrDiff = process.hrtime(httpTest.startTime);
const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6;
const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString();
console.info(`http.Server handled an average of ${requestsPerSecond} requests per second.`);
}
});
}
while (httpTest.testCount < testLimit) {
httpTest.testCount++;
payloadData.test = httpTest.testCount;
payloadData.feet++;
const payload = JSON.stringify(payloadData);
options[`Content-Length`] = Buffer.byteLength(payload);
const req = http.request(options, handleResponse);
req.end(payload);
}
}
/* -- Start tests -- */
// flip these occasionally to ensure there's no bias based on order
setTimeout(() => {
console.info(`Starting testNetClient()`);
testNetClient();
}, 50);
setTimeout(() => {
console.info(`Starting testHttpClient()`);
testHttpClient();
}, 2000);
Вот – server.js.
// Здесь созданы два сервера. Один – TCP, второй – HTTP.
// Для каждого запроса серверы преобразуют полученную строку в объект JSON, формируют с его использованием новую строку, и отправляют её в ответ на запрос.
const net = require(`net`);
const http = require(`http`);
function renderAnimalString(jsonString) {
const data = JSON.parse(jsonString);
return `${data.test}: your are a ${data.type} and you have ${data.feet} feet.`;
}
/* ------------------ */
/* -- NET server -- */
/* ------------------ */
net
.createServer((socket) => {
socket.on(`data`, (jsonString) => {
socket.end(renderAnimalString(jsonString));
});
})
.listen(8888);
/* ------------------- */
/* -- HTTP server -- */
/* ------------------- */
function parseIncomingMessage(res) {
return new Promise((resolve) => {
let data = ``;
res.on(`data`, (chunk) => {
data += chunk;
});
res.on(`end`, () => resolve(data));
});
}
http
.createServer()
.listen(8080)
.on(`request`, (req, res) => {
parseIncomingMessage(req).then((jsonString) => {
res.end(renderAnimalString(jsonString));
});
});
19. Хитрости режима REPL
- Если вы работаете в режиме REPL, то есть, написали в терминале
node
и нажали на Enter, можете ввести команду вроде.load someFile.js
и система загрузит запрошенный файл (например, в таком файле может быть задана куча констант).
- В этом режиме можно установить переменную окружения
NODE_REPL_HISTORY=""
для того, чтобы отключить запись истории в файл. Кроме того, я узнал (как минимум – вспомнил), что файл истории REPL, который позволяет путешествовать в прошлое, хранится по адресу~/.node_repl_history
.
- Символ подчёркивания «
_»
— это имя переменной, которая хранит результат последнего выполненного выражения. Думаю, может пригодиться.
- Когда Node запускается в режиме REPL, модули загружаются автоматически (точнее – по запросу). Например, можно просто ввести в командной строке
os.arch()
для того, чтобы узнать архитектуру ОС. Конструкция вродеrequire(`os`).arch();
не нужна.
Итоги
Как видите, читать документацию – дело полезное. Много нового можно найти даже в той области, которую, вроде бы, знаешь вдоль и поперёк. Надеюсь, вам пригодятся мои находки.
Кстати, знаете ещё что-нибудь интересное о Node.js? Если так – делитесь :)
Комментарии (70)
rumkin
23.12.2016 17:29+8Даже, если из всей статьи оставить только пункт 2, то эта статья не потеряет ценности. Потому что это просто неописуемо круто, когда можно дебажить удаленный проект так просто. В браузере.
yogurt1
23.12.2016 17:37-13Вы серьезно? Как вообще можно разрабатывать под какую-нибудь платформу, не зная документации? Я считаю, что это статься не достойна главной, так как тут ничего нового для среднего разработчика Node.js не найти
Документация Node.js очень маленькая и простая, потому что Node.js по сути обретка над различными библиотеками, в отличии от PHP, Ruby, Python и других языков, где стандартные библиотеки богаты различными интересными штуками, о которых в документации если и есть что-то, то это нужно хорошенько поискатьsentyaev
23.12.2016 20:40+8Вы серьезно? Как вообще можно разрабатывать под какую-нибудь платформу, не зная документации?
Очень легко. Читаем Getting started, кодим. Если не получается читаем StackOverflow, кодим дальше.
И только спустя какое-то время начинаем читать доку.
Но обычно пишем на SO вопросы или находим новый модный фрейворк и все сначала.olexandr17
24.12.2016 23:27мне кажется, такой стиль программирования погубил flash.
потому что, платформа отличная, но низкий порог вхождения все испортил.ckr
25.12.2016 01:25+2Flash тоже не святой. столько нервов отнял не только у web-мастеров, но и у пользователей.
В смерти flash вините соцсети. Сколько раз из-за него только мой системник вырубался от перегрева.
Еще помню как со злости проломил динамик в microlab pro 1 (правда, мне было не жалко, они все равно хрипели).
Dimly
24.12.2016 20:35У меня была дискуссия с руководителем — кем является программист, творцом или ремесленником.
Вы убедили меня в том что некоторые — все таки ремесленники. Открыть мануал, повторить что написано, скомпоновать по своему — это обычный «кодинг», и даже в этом случае нет гарантий что вы давным давно прочитав описание неких методов — запомнили это и теперь используете. Вероятнее — вы помните что читали, и не читаете повторно, но забыли о чем и не используете.
Ведь согласитесь вы не перечитываете мануалы при апдейтах? А ведь алгоритм встроенных методом мог изменится, и тот что вы не использовали ранее из-за тормозов, стал оптимальнее текущих ваших приемов.
Буквально вчера читал даже не описание, а подсказку к настройке плагина Atlassian, где написано «в данной версии умеем присваивать только цифры и текст». Они не указали «списки выбора», и если бы я поверил — то не стал бы использовать для присвоения enumeration, которые внутри все таки цифры, но пойди их еще найди.
Shadow_ru
23.12.2016 18:42+1querystring сконвертит все что найдет в строках urlencoded. Сомнительный хак.
ChALkeRx
23.12.2016 18:44+2process.on('uncaughtException', console.error);
Не делайте так. Тем более для «предотвращения остановок сервера».
Ссылка на доку: https://nodejs.org/dist/latest-v7.x/docs/api/process.html#process_event_uncaughtexception
конечно, нахожусь в здравом уме, поэтому пользуюсь PM2
PM2 в вакууме тоже не является гарантией того, что всё это не упадёт. В продакшне надо использовать системные менеджеры для запуска всего, независимо от наличия PM2 (который может использоваться для других целей).
у объектов EventEmitter имеется и метод
code
code
? Это ошибка?zekohina
23.12.2016 22:49В продакшне надо использовать системные менеджеры для запуска всего
Можно подробнее? Что это за менеджеры?ChALkeRx
23.12.2016 23:43+2На совеременных Linux-дистрибутивах это systemd, см. http://0pointer.de/blog/projects/watchdog.html.
На более старой убунте и ещё нескольких дистрибутивах это upstart.
На чём-то ещё — другие системные средства, которые гарантируют работу сервисов.
Полагаться на то, что запущенный без какого-либо надсмотрщика pm2/forever/чтоещё сам не упадёт — довольно безумно в продакшне.
bobelev
24.12.2016 08:59+1PM2 в последних релизах сделал интеграцию с systemd/upstart, так что теперь чуточку удобнее.
ChALkeRx
24.12.2016 16:12Ага, спасибо, я взгляну на это чуть позже, интересно, как это реализовано и поддерживают ли они watchdog.
Когда я последний раз смотрел, у pm2 была целая куча жалоб в issues где пользователи запускали его в отрыве от всего а потом ловили внезапный oom, который убивал pm2 и очень удивлялись.
По-хорошему, таким утилитам нужна не только опциональная поддержка интеграции, но ещё и активное напоминание о том, что её всё-таки стоит включить (если его там ещё нет).
Сейчас у меня довольно плохо всё со временем, но я себе записал в список дел посмотреть на то, как именно в pm2 организовали интеграцию с systemd/upstart и прокомментировать, если я замечу в этом косяки.
izonder
23.12.2016 18:45+2Касательно п.5: будьте осторожны, без __dirname иногда не обойтись, например, когда ваш компонент подключается как npm-модуль, в данном случае require справится, а вот fs.readFile — нет (старый добрый ENOENT).
RuGrof
23.12.2016 22:49+2Практически везде, кроме require, относительный путь берётся от директории запуска.
Если вы запускается скрипт из /~ или C:\ то все пути fs.readFile('./...') будут идти от этой директории, в то время как в require путь берётся от файла, где он вызван.
Disbeleiver
24.12.2016 08:03+1На текущую директорию в серверном приложении вообще полагаться нельзя (разве что если вы временные файлы создаете, да и для тех tmpfs есть). Она зависит от того, какой командой и каким обработчиком запускается приложение, и совершенно не факт, что она совпадет с директорией, в которой лежат ваши файлы.В зависимости от фантазии админа это можент быть как ~ или /root/, так и /etc/cron.d/ или F:/MUSTDIE.
Aingis
23.12.2016 20:084. Когда Node запускается в режиме REPL, модули загружаются автоматически (точнее – по запросу). Например, можно просто ввести в командной строке os.arch() для того, чтобы узнать архитектуру ОС. Конструкция вроде require(`os`).arch(); не нужна.
Системные модули подгружаются, а вот, скажем, lodash нет. Хуже того, если сделатьrequire('lodash')
, то после выполнения следующей команды модуль куда-то пропадает.ChALkeRx
23.12.2016 20:32+1В смысле — пропадает? Покажите конкретную последовательность команд, пожалуйста.
Mithgol
24.12.2016 02:40+3Если сделать просто
«require('lodash')», но не присвоить присвоитьрезультат «require('lodash')» в явном виде той переменной, имя которой состоит из знака подчёркивания (такое имя переменной чаще всего и используется пользователями lodash), то тогда этот результат присвоится этой переменной автоматически, но не менее автоматически пропадёт (то есть переменная эта переприсвоится) на следующем же шаге REPL — оттого, что в REPL переменная с таким именем имеет особый смысл, что также сказанов документации Node.js.
rayz_razko
23.12.2016 20:50+2Заголовок обещал неожиданные находки, а статья об очевидных вещах, о которых автор не знал т.к. не читал документацию.
ChALkeRx
23.12.2016 20:56+1Я помню как один раз у нас в документации
<<<<<<< HEAD
нашёлся, вот это было неожиданно.
Но с тех пор добавилась проверка на них на этапе линта на CI.
Alex_Crack
23.12.2016 22:49Я запускаю проект так:
NODE_PATH=. node app.js
и все модули ищутся относительно текущей директории. Соответсвенно в продакшне используется pm2 и для него создается список процессов processes.json, где также задается эта переменная окружения.ChALkeRx
23.12.2016 23:47+1Не совсем понял, как это относится к моему комментарию выше, но ладно.
Так же я не понял, зачем вам нужен NODE_PATH, и почему вы не кладёте зависимости в папку приложения.
Лучше всего NODE_PATH не пользоваться вообще и забыть про него, он оставлен для совместимости.
ckr
24.12.2016 03:40+1Переопределение NODE_PATH на корень своего проекта — совсем не тру-практика.
Здравым смыслом подразумевается, что в переменной NODE_PATH хранится корень установленного NodeJS.ChALkeRx
24.12.2016 16:16+1Здравым смыслом подразумевается, что в переменной NODE_PATH хранится корень установленного NodeJS.
Если честно, я не понял мысли. Можете показать на примере путей, что вы имеете ввиду?
К слову о «тру-практиках» — использование
NODE_PATH
вообще не рекомендовано.ckr
24.12.2016 21:40По умолчанию при установке NodeJS переменная NODE_PATH не задается в окружение. Оставить ее пустой или незаданной гораздо лучше, чем использовать не по назначению. Я могу и ошибаться. Но настраивать NODE_PATH в корень одного проекта — это может быть уместно если на одной машине один проект и, соответственно, запускается один app.js.
В любом случае, вопрос развертывания к сисадмину. Разрабатывать на локальной машине проекты можно так, как удобно разработчику.ckr
24.12.2016 21:50Скорее всего, по поводу тру- или нетру- практики я перегнул палку. Тут нет однозначного ответа. В продакшене, обычно переменные, именуемые КАПСом у меня имеют одно значение для всех скриптов, заданное в profile при загрузки ОС. Тут Node не одинок. Среди переменных есть GOPATH, ROR_PATH…
safecode
01.01.2017 15:23Вещи, может, очевидные, а статья полезная, т.к. не всегда вдумываешься в то, что написано. Смотришь только на параметры. К примеру, я привык на PHP к тому, что array_slice возвращает часть массива, и эту часть можно изменять. А в Node.js нельзя изменять часть буфера, если не нужно менять оригинал.
koutsenko
23.12.2016 21:46+1Раз речь зашла о системных мелочах Node.js.
Хотел бы спросить.
Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?
Ограничение «только относительные пути» ухудшает читабельность и рефакторинг.
Насчет упаковки включаемого кода в node_modules в курсе — не выход (неудобно).Zenitchik
23.12.2016 22:12+1Я правильно понял, что у вас есть файл moduleB.js и из него вы хотите подключить файл, расположенный по отношению к нему по пути ../../../../moduleA.js и чтобы было как-то типа require(«moduleA»)?
Я что-то такое делал, сейчас поищу.Zenitchik
23.12.2016 22:20Можно добавить в module.paths путей папок, в которых будет искаться модуль. Выведите в консоль, что у Вас содержится в этом свойстве и сможете придумать какую логику над этим надо провернуть.
koutsenko
23.12.2016 22:58Значение module.paths вычисляемое и различается для модулей в разных каталогах…
Вообще require поддерживает абсолютные пути, но относительно корня текущего диска.
Может быть стоит где-нить в main.js кроссплатформенно вычислить абсолютный путь до корня проекта.
Записать его в глоб. переменную (одна переменная — терпимо) и дальше везде тупо собирать полный путь…Zenitchik
23.12.2016 23:01Путь вида «корень-проекта/node_modules» наверняка уже содержится в module.paths. Опознать его и обрезать node_modules, я думаю, не слишком сложно.
koutsenko
23.12.2016 23:18Кажется, хватит тупого сохранения значения __dirname в main.js.
Прокатит даже для клиентского js-кода где за require-кухню отвечает webpack.
Только придется поддержку __dirname ему в конфиге включить явно.
koutsenko
24.12.2016 03:02итого, в точке входа в корне проекта пишу
global.path = require('path'); global.root = __dirname;
инклуды в других файлах, было:
var module = require('../../../../moduleA');
стало:
var module = require(path.resolve(root, 'controller/net/moduleA'));
проверил на Win\Mac — нормальноiShatokhin
25.12.2016 00:07А зачем засорять global? Можно и так:
var module = require(path.resolve(process.cwd(), 'controller/net/moduleA'));
koutsenko
23.12.2016 22:44Да, верно… Для разового включения это терпимо.
Но допустим, этот moduleA.js включен много где в файлах расположенных разных по уровню каталогах.
Каждый раз приходится «спускаться» до корня проекта вручную, потом «подниматься» до модуля.
Либо задумываться о замене CommonJS-вского require на что-то более умное.
Либо делать костыли — заранее в main.js подключить всё и сделать глобальным (с неймспейсами если надо):
global.ModuleA = require('./moduleA.js')Zenitchik
23.12.2016 22:55Можно создать свой модуль и экспортировать его.
var Module = module.constructor; var m = new Module(''); handle(m.paths); //Внесли нужные правки module.exports = m;
Использовать потом — m.require
Или можно покопаться в process.mainModule.paths, а потом использовать process.mainModule.require
UPD,
Да, почему бы не положить где-то в более удобном месте
module.exports = require('../../../../moduleA')?
justboris
23.12.2016 23:571) Посмотрите на NODE_PATH
2) Но вообще, если у вас есть такие длинные пути в require, то почему бы просто не перенести зависимые модули в папки поближе друг другу?
koutsenko
24.12.2016 03:151) Вариант если пути к модулям неизменны и их мало. Слово «модуль» в контексте «файл с js-кодом».
2) Вариант если нет фреймворка или иерархии компонент в сочетании например с Redux
Впрочем, пока для себя определился.
gearbox
24.12.2016 16:39+1>Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?
можно начать собирать webpack-ом, там резолвинг настраивается. А можно начать использовать typescript — module resolution. В общем инстументы есть.
le1ic
24.12.2016 00:30+1Код в п.5 делает совершенно разные вещи и невзаимозаменяем.
п6 есть почти в любом языке программирования, как можно этого не знать.
пп 9,11,12 — все про одно и тоже и тоже довольно очевидно (сюрпризом было бы отсутствие констант) для всех, кто хоть раз читал описание любого нормального API.to0n1
24.12.2016 02:09можете по п5 подробнее?
с остальным согласен более чем полностью!ckr
24.12.2016 03:09+3Относительные пути работают не только с модулем fs.Тут все гораздо более глобально. Это относится не столько к NodeJS, сколько к основам принципов работы операционных систем.
le1ic
24.12.2016 14:08+1Первый код считает путь начиная месторасположения текущего файла. Второй код считает от current working directory текущего процесса, что в общем случае директория откуда выполнен запуск.
https://nodejs.org/docs/latest/api/globals.html#globals_dirname
ckr
24.12.2016 02:50+1Модуль net вдвое быстрее модуля http
Спорное утверждение. Это как сравнивать теплое с мягким. Я бы утверждал что скорость их работы при заданных условиях работы приблизительно одинаковая.
В результате http.Server смог обработать примерно 3400 запросов в секунду, а net.Server – примерно 5500. К тому же, net.Server проще устроен.
Ваша статистическое утверждение никак не может быть объективным. Во-первых, вы пренебрегаете keep-alive-соединениями. Во-вторых, количеством одновременных соединений.
Конечно же, используя net можно написать реализацию любого протокола. Но, в большинстве случаев отладка своего протокола может превратиться в ад. Для большинства случаев подходит простенький и экстремально быстрый пакет ws, основанный на WebSocket. Удобно это прежде всего тем, что двухсторонний обмен данными происходит сообщениями из js-объектов. Это позволяет не заморочиваться с проектированием протокола обмена, можно добавлять и удалять параметры в сообщения и их обработку в любой момент разработки, что повысит гибкость работы с версиями API. Также, если не ошибаюсь, через объект этого сообщения можно передавать функции, содержащие произвольный js-код. Ну, и нельзя отметать возможность отлаживать взаимодействие с помощью браузера.
ckr
24.12.2016 03:31+1Хочется также поздравить автора с эволюционированием скилла разработчика до продвинутого разработчика.
Внимательное изучение API-документации используемых средств разработки происходит далеко не в самом начале карьеры.
Следующий этап развития разработчика (назовем его, например, гуру разработчик) происходит, когда разработчик изучает используемые средства по исходным кодам этих средств.
Тут я серьезно! Когда все API-документации средств изучены, тяга к познаниям обязательно заставит изучать используемые в разработке средства по исходным кодам.
ckr
24.12.2016 03:50В добавление к п.2 хочется отметить наличие readline-интерфейса. Тоже неплохая альтернатива отладки без использования браузера.
ckr
24.12.2016 22:42Посмотрел в документацию и сам чуть не поседел. Столько всего нового, что вообще не понятно, что я имел в виду, когда писал комментарий выше.
NodeJS. как и многие другие инструментальные средства по Unix-традициям запускается тремя способами:
- путь к файлу скрипта предается параметром
node script.js
; - скрипт передается процессу node через стандартный поток ввода
cat script.js | node
; - или просто вручную запускаем node без параметров
node
.
Ручной запуск ноды без параметров инициализирует readline-интерфейс. Здесь нода будет выполнять скрипт по-строчно с теми же возможностями, как и в консоли браузера.
Главный файл проекта можно подключить параметром запуска--require
или функциейrequire()
или дажеimport
. А после подключения главного модуля, сервер или что там подключали работает, а во время выполнения этого процесса можно его отлаживать, например, проверяя значения переменных или вручную вызывая различные функции, влияющие на состояние процесса.- путь к файлу скрипта предается параметром
Mingun
24.12.2016 13:29+2Возможно вы заметили, что в примерах я использую для строк одинарные кавычки.
Вообще-то «backticks» — это обратные кавычки. Одинарные — это «single quotes» (
'
)ckr
24.12.2016 20:22Вот дела, а я сам и не заметил сразу).
Ничего не имею против es6-шаблонов, определенных таким образом. Но использовать их вместо статических строк не рационально. Автору советую лучше протестировать производительность шаблонов и статических строк. Интересно, как помогает автору pm2 в отладке?! Автор, какие средства используете для борьбы с утечками?ChALkeRx
25.12.2016 00:23+2Интересно, как помогает автору pm2 в отладке?! Автор, какие средства используете для борьбы с утечками?
Автора в этой теме нет, вы говорите с переводом на русский язык, размещённом в блоге хостера VDS/VPS.
superconductor
24.12.2016 20:34+1<зануда mode>
Backticks — это все-таки апострофы, а не одинарные кавычки.
</зануда mode>
Странно сравнивать производительность net и http, учитывая, что второй должен не просто данные собрать, но и http распарсить.
Если я ничего не путают http использует net внутри для собственно работы с сетью.
И я так и не понял, почему 5500/3400 — это "вдвое быстрее".
sutarmin
26.12.2016 11:44+2Тут выше писали о том, что backtick — это не просто одинарная кавычка. Вставлю свои 5 копеек по поводу удобства использования именно ` вместо " и '. Можно делать вот так:
let a = 10; let b = 15; console.log(`Сумма: ${a+b}`); // Выведет Сумма: 25 // Внутрь ${} можно подставлять любое js-выражение var str = `Логарифм: ${Math.log(10)}`); //str == 'Логарифм: 2.302585092994046'
Очень удобный способ форматирования строк, на мой взгляд.
phgrey
27.12.2016 13:23Поправьте пожалуйста пункт 5:
__dirname не имеет отношение к cwd, __dirname — это путь к папке с файлом, в котором она написана.
scripts/test.js
console.log(__dirname)
$ node scripts/test.js /path/to/your/project/scripts
Такие ошибки запутывают новичков и приучают работать с абсолютными путями)))))
bubuq
27.12.2016 15:46+1require(`net`).isIP(`cats`)
Возможно вы заметили, что в примерах я использую для строк одинарные кавычки.
Мы заметили, что вы используете не одинарные кавычки, а обратные апострофы.
Было бы оправдано, если бы вы использовали шаблоны:
require('net').isIP(`${ myPet }s`)
но вы ведь нет, так что зачем?
Germanets
Интересный ответ вместо 'Not Found' выдаёт на скриншоте http.STATUS_CODE для кода 404, видимо это собственное изобретение автора.
P.S. увидел, что перевод, исправил.