- PVSM.RU - https://www.pvsm.ru -
Мне хотелось бы думать, что я вполне прилично знаю Node. Вот уже три года, как ни один из сайтов, над которыми я работал, не обходится без него. Но документацию до сих пор я как следует не читал.
Мне нравится записывать полезные вещи об интерфейсах, свойствах, методах, функциях, типах данных, и обо всём прочем, что относится к веб-разработке. Так я заполняю пробелы в знаниях. Сейчас я занят документацией к Node.js, а до этого проработал материалы по HTML, DOM, по Web API, CSS, SVG и EcmaScript.
Чтение документации Node.js открыло мне немало замечательных вещей, о которых я раньше не знал. Ими я хочу поделиться в этом небольшом материале. Начну с самого интересного. Так же я обычно делаю, когда показываю новому знакомому свои гаджеты.
Скажем, вы получили данные из какой-нибудь эксцентричной БД, которая выдала массив пар ключ/значение в примерно таком виде:
name:Sophie;shape:fox;condition:new
. Вполне естественно полагать, что подобное можно легко преобразовать в объект JavaScript. Поэтому вы создаёте пустой объект, затем – массив, разделив строку по символу «;
». Дальше – проходитесь в цикле по каждому элементу этого массива, опять разбиваете строки, теперь уже по символу «:
». В итоге, первый полученный из каждой строки элемент становится именем свойства нового объекта, второй – значением.
Всё правильно?
Нет, не правильно. В подобной ситуации достаточно воспользоваться querystring
[1].
const weirdoString = `name:Sophie;shape:fox;condition:new`;
const result = querystring.parse(weirdoString, `;`, `:`);
// результат:
// {
// name: `Sophie`,
// shape: `fox`,
// condition: `new`,
// };
Если запустить Node с ключом --inspect
, он сообщит URL. Перейдите по этому адресу в Chrome. А теперь – приятная неожиданность. Нам доступна отладка Node.js [2] с помощью инструментов разработчика Chrome. Настали счастливые времена. Вот руководство [3] на эту тему от Пола Айриша.
Надо отметить, что данная функция всё ещё носит статус экспериментальной, но я ей с удовольствием пользуюсь и до сих пор она меня не подводила.
Как и в случае со многими другими программными механизмами, запомнить разницу между этими двумя функциями очень просто, если дать им более осмысленные имена.
Итак, функция process.nextTick()
[4] должна называться process.sendThisToTheStartOfTheQueue()
. А setImmediate()
[5] - sendThisToTheEndOfTheQueue()
.
Кстати, вот полезный материал об оптимизации [6] nextTick
начиная с Node v0.10.0. Маленькое отступление. Я всегда думал, что в React props
должно называться stuffThatShouldStayTheSameIfTheUserRefreshes
, а state
– stuffThatShouldBeForgottenIfTheUserRefreshes
. То, что у этих названий одинаковая длина, считайте удачным совпадением.
Я приверженец передачи параметров в виде объекта, например, с именем «options», а не подхода, когда на входе в функцию ожидается куча параметров, которые, к тому же, не имеют имён, да ещё и должны быть расположены в строго определённом порядке. Как оказалось, при настройке сервера на прослушивание запросов [7] можно использовать объект с параметрами.
require(`http`)
.createServer()
.listen({
port: 8080,
host: `localhost`,
})
.on(`request`, (req, res) => {
res.end(`Hello World!`);
});
Эта полезная возможность неплохо спряталась. В документации по http.Server
[8] о ней – ни слова. Однако, её можно найти в описании net.Server
[9], наследником которого является http.Server
.
Путь в файловой системе, который передают модулю fs
[10], может быть относительным. Точка отсчёта – текущая рабочая директория, возвращаемая 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) => {
// делаем что-нибудь полезное
});
Обычно, когда мне нужно было вытащить из пути к файлу его имя и расширение, я пользовался регулярными выражениями. Теперь понимаю, что в этом нет совершенно никакой необходимости. То же самое можно сделать стандартными средствами [11].
myFilePath = `/someDir/someFile.json`;
path.parse(myFilePath).base === `someFile.json`; // true
path.parse(myFilePath).name === `someFile`; // true
path.parse(myFilePath).ext === `.json`; // true
Сделаю вид, будто я не знал, что конструкция console.dir(obj, {colors: true})
позволяет выводить в консоль [12] объекты со свойствами и значениями, выделенными цветом. Это упрощает чтение логов.
Например, вы используете setInterval()
для того, чтобы раз в день проводить очистку базы данных. По умолчанию цикл событий Node не остановится до тех пор, пока имеется код, исполнение которого запланировано с помощью setInterval()
. Если вы хотите дать Node отдохнуть (не знаю, на самом деле, какие плюсы можно от этого получить), воспользуйтесь функцией unref()
[13].
const dailyCleanup = setInterval(() => {
cleanup();
}, 1000 * 60 * 60 * 24);
dailyCleanup.unref();
Однако, тут стоит проявить осторожность. Если Node больше ничем не занят (скажем, нет http-сервера, ожидающего подключений), он завершит работу.
Если вам нравится убивать, то вы, наверняка, уже так делали:
process.kill(process.pid, `SIGTERM`);
Ничего плохого об этой конструкции сказать не могу. Но что, если в команду вкралась ошибка, вызванная опечаткой? В истории программирования известны такие случаи. Второй параметр здесь должен быть строкой или соответствующим целым числом, поэтому тут немудрено написать что-нибудь не то. Для того, чтобы застраховаться от ошибок, можно поступить так:
process.kill(process.pid, os.constants.signals.SIGTERM);
В Node.js имеется встроенное средство для проверки IP-адресов [14]. Раньше я не раз писал регулярные выражения для того, чтобы это сделать. На большее ума не хватило. Вот как это сделать правильно:
require(`net`).isIP(`10.0.0.1`)
вернёт 4
.
require(`net`).isIP(`cats`)
вернёт 0
.
Всё верно, коты – это не IP-адреса.
Возможно вы заметили, что в примерах я использую для строк одинарные кавычки. Мне так делать нравится, но я подозреваю, что выглядит это странно, поэтому считаю нужным об этом упомянуть, хотя и сам толком не знаю – зачем. В общем – это мой стиль.
Вы когда-нибудь задавали в коде символ конца строки? Да? Всё, тушите свет. Вот, специально для тех, кто так делал, замечательная штука: os.EOL
[15]. В Windows это даст rn
, во всех остальных ОС — n
. Переход на os.EOL
[16] позволит обеспечить единообразное поведение кода в разных операционных системах.
Тут я сделаю поправку, так как в момент написания материала недостаточно в эту тему углубился. Читатели предыдущей версии этого поста указали мне на то, что использование os.EOL
может приводить к неприятностям. Дело в том, что здесь нужно исходить из предположения, что в некоем файле может использоваться или CRLF(rn
), или LF (n
), но полностью быть уверенным в подобном предположении нельзя.
Если у вас имеется проект с открытым исходным кодом, и вы хотите принудительно использовать определённый вариант перевода строки, вот правило eslint [17], которое, отчасти, может в этом помочь. Правда, оно бесполезно, если с текстами поработает Git.
И, всё же, os.EOL
– не бесполезная игрушка. Например, эта штука может оказаться кстати при формировании лог-файлов, которые не планируется переносить в другие ОС. В подобном случае os.EOL
обеспечивает правильность отображения таких файлов, скажем, для просмотра которых используется Блокнот в Windows Server.
const fs = require(`fs`);
// жёстко заданный признак конца строки CRLF
fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
data.split(`rn`).forEach(line => {
// делаем что-нибудь полезное
});
});
// признак конца строки зависит от ОС
const os = require(`os`);
fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
data.split(os.EOL).forEach(line => {
// делаем что-нибудь полезное
});
});
В Node имеется «справочник» с кодами состояния HTTP и их названиями. Я говорю об объекте http.STATUS_CODE
[18]. Его ключи – это коды состояний, а значения – их названия.
Объект http.STATUS_CODE
Вот как этим пользоваться:
someResponse.code === 301; // true
require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true
Мне всегда казалось малость странным то, что код, похожий на приведённый ниже, приводит к остановке сервера.
const jsonData = getDataFromSomeApi(); // Только не это! Нехорошие данные!
const data = JSON.parse(jsonData); // Громкий стук падающего сервера.
Для того, чтобы предотвратить подобные глупости, прямо в начале приложения для Node.js можно поместить такую конструкцию, выводящую необработанные исключения [19] в консоль:
process.on(`uncaughtException`, console.error);
Я, конечно, нахожусь в здравом уме, поэтому пользуюсь PM2 [20] и оборачиваю всё, что можно, в блоки try…catch
, когда программирую на заказ, но вот в домашних проектах…
Хочу обратить особое внимание на то, что такой подход никоим образом не относится к «лучшим практическим методам разработки [21]», и его использование в больших и сложных приложениях, вероятно, идея плохая. Решайте сами, доверять ли посту в блоге, написанному каким-то чуваком, или официальной документации.
В дополнение к методу on()
, у объектов EventEmitter
имеется и метод code
[22]. Я совершенно уверен, что я – последний человек на Земле, который об этом узнал. Поэтому ограничусь простым примером, который все и так поймут.
server.once(`request`, (req, res) => res.end(`No more from me.`));
Консоль [23] можно настроить с помощью нижеприведённой конструкции, передавая ей собственные потоки вывода:
new console.Console(standardOut, errorOut)
Зачем? Не знаю точно. Может, вы захотите создать консоль, которая выводит данные в файл, или в сокет, или ещё куда-нибудь.
Мне тут одна птичка насвистела, что Node не кэширует [24] результаты запросов к DNS. Поэтому, если вы несколько раз обращаетесь к некоему URL, на запросы, без которых можно было бы обойтись, тратятся бесценные миллисекунды. В подобном случае можно выполнить запрос к DNS самостоятельно, с помощью dns.lookup()
[25], и закэшировать результаты. Или – воспользоваться пакетом dnscache [26], который делает то же самое.
dns.lookup(`www.myApi.com`, 4, (err, address) => {
cacheThisForLater(address);
});
Если ваш стиль программирования похож на мой, то есть, это что-то вроде: «прочту по диагонали кусок документации и буду возиться с кодом, пока он не заработает», тогда вы не застрахованы от проблем с модулем fs
[27]. Разработчики выполнили огромную работу, направленную на унификацию взаимодействия 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, отошло в мир иной).
Читая документацию к Node.js, я понял, что модуль net
[28] – это вещь. Он лежит в основе модуля http
. Это заставило меня задуматься о том, что если нужно организовать взаимодействие серверов (как оказалось, мне это понадобилось), стоит ли использовать исключительно модуль net
?
Те, кто плотно занимается сетевым взаимодействием систем, могут и не поверить, что подобный вопрос вообще надо задавать, но я – веб-разработчик, который вдруг свалился в мир серверов и знает только HTTP и ничего больше. Все эти TCP, сокеты, вся эта болтовня о потоках… Для меня это как японский рэп [29]. То есть, мне вроде бы и непонятно, но звучит интригующе.
Для того, чтобы во всём разобраться, поэкспериментировать с net
и http
, и сравнить их, я настроил пару серверов (надеюсь, вы сейчас слушаете японский рэп) и нагрузил их запросами. В результате http.Server
[8] смог обработать примерно 3400 запросов в секунду, а net.Server
[30] – примерно 5500. К тому же, net.Server
проще устроен.
Вот, если интересно, код клиентов и серверов, с которым я экспериментировал. Если не интересно – примите извинения за то, что вам придётся так долго прокручивать страницу.
Вот код client.js [31].
// Здесь создаются два подключения. Одно – к 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 [32].
// Здесь созданы два сервера. Один – 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));
});
});
node
и нажали на Enter, можете ввести команду вроде .load someFile.js
и система загрузит запрошенный файл (например, в таком файле может быть задана куча констант).
NODE_REPL_HISTORY=""
для того, чтобы отключить запись истории в файл. Кроме того, я узнал (как минимум – вспомнил), что файл истории REPL, который позволяет путешествовать в прошлое, хранится по адресу ~/.node_repl_history
.
_»
— это имя переменной, которая хранит результат последнего выполненного выражения. Думаю, может пригодиться.
os.arch()
для того, чтобы узнать архитектуру ОС. Конструкция вроде require(`os`).arch();
не нужна.
Как видите, читать документацию – дело полезное. Много нового можно найти даже в той области, которую, вроде бы, знаешь вдоль и поперёк. Надеюсь, вам пригодятся мои находки.
Кстати, знаете ещё что-нибудь интересное о Node.js? Если так – делитесь :)
Автор: RUVDS.com
Источник [34]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/224476
Ссылки в тексте:
[1] querystring
: https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
[2] отладка Node.js: https://nodejs.org/api/debugger.html#debugger_v8_inspector_integration_for_node_js
[3] руководство: https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27#.evhku718w
[4] process.nextTick()
: https://nodejs.org/api/process.html#process_process_nexttick_callback_args
[5] setImmediate()
: https://nodejs.org/api/timers.html#timers_setimmediate_callback_args
[6] оптимизации: https://nodejs.org/en/blog/release/v0.10.0/#faster-process-nexttick
[7] прослушивание запросов: https://nodejs.org/api/net.html#net_net_createserver_options_connectionlistener
[8] http.Server
: https://nodejs.org/api/http.html#http_class_http_server
[9] net.Server
: https://nodejs.org/api/net.html#net_server_listen_options_callback
[10] fs
: https://nodejs.org/api/fs.html#fs_file_system
[11] стандартными средствами: https://nodejs.org/api/path.html#path_path_parse_path
[12] выводить в консоль: https://nodejs.org/api/console.html#console_console_dir_obj_options
[13] unref()
: https://nodejs.org/api/timers.html#timers_timeout_unref
[14] проверки IP-адресов: https://nodejs.org/api/net.html#net_net_isip_input
[15] os.EOL
: https://nodejs.org/api/os.html#os_os_eol
[16] Переход на os.EOL
: https://github.com/sasstools/sass-lint/pull/92/files
[17] правило eslint: http://eslint.org/docs/rules/linebreak-style
[18] http.STATUS_CODE
: https://nodejs.org/api/http.html#http_http_status_codes
[19] необработанные исключения: https://nodejs.org/api/process.html#process_event_uncaughtexception
[20] PM2: http://pm2.keymetrics.io/
[21] лучшим практическим методам разработки: https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly
[22] code
: https://nodejs.org/api/events.html#events_emitter_once_eventname_listener
[23] Консоль: https://nodejs.org/api/console.html#console_new_console_stdout_stderr
[24] не кэширует: https://github.com/nodejs/node/issues/5893
[25] dns.lookup()
: https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback
[26] dnscache: https://www.npmjs.com/package/dnscache
[27] модулем fs
: https://nodejs.org/api/fs.html
[28] net
: https://nodejs.org/api/net.html
[29] японский рэп: https://www.youtube.com/watch?v=FQgH4G3qypI
[30] net.Server
: https://nodejs.org/api/net.html#net_class_net_server
[31] client.js: https://gist.github.com/davidgilbertson/8c38f7604eb95cec2836be68217e643b#file-client-js
[32] server.js: https://gist.github.com/davidgilbertson/8c38f7604eb95cec2836be68217e643b#file-server-js
[33] REPL: https://nodejs.org/api/repl.html
[34] Источник: https://habrahabr.ru/post/318322/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.