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

Мне нравится записывать полезные вещи об интерфейсах, свойствах, методах, функциях, типах данных, и обо всём прочем, что относится к веб-разработке. Так я заполняю пробелы в знаниях. Сейчас я занят документацией к Node.js, а до этого проработал материалы по HTML, DOM, по Web API, CSS, SVG и EcmaScript.

image

Чтение документации 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, а statestuffThatShouldBeForgottenIfTheUserRefreshes. То, что у этих названий одинаковая длина, считайте удачным совпадением.

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


  1. Если вы работаете в режиме REPL, то есть, написали в терминале node и нажали на Enter, можете ввести команду вроде .load someFile.js и система загрузит запрошенный файл (например, в таком файле может быть задана куча констант).

  2. В этом режиме можно установить переменную окружения NODE_REPL_HISTORY="" для того, чтобы отключить запись истории в файл. Кроме того, я узнал (как минимум – вспомнил), что файл истории REPL, который позволяет путешествовать в прошлое, хранится по адресу ~/.node_repl_history.

  3. Символ подчёркивания « — это имя переменной, которая хранит результат последнего выполненного выражения. Думаю, может пригодиться.

  4. Когда Node запускается в режиме REPL, модули загружаются автоматически (точнее – по запросу). Например, можно просто ввести в командной строке os.arch() для того, чтобы узнать архитектуру ОС. Конструкция вроде require(`os`).arch(); не нужна.

Итоги


Как видите, читать документацию – дело полезное. Много нового можно найти даже в той области, которую, вроде бы, знаешь вдоль и поперёк. Надеюсь, вам пригодятся мои находки.

Кстати, знаете ещё что-нибудь интересное о Node.js? Если так – делитесь :)
Поделиться с друзьями
-->

Комментарии (70)


  1. Germanets
    23.12.2016 17:23
    +4

    Интересный ответ вместо 'Not Found' выдаёт на скриншоте http.STATUS_CODE для кода 404, видимо это собственное изобретение автора.

    P.S. увидел, что перевод, исправил.


  1. rumkin
    23.12.2016 17:29
    +8

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


  1. zxcabs
    23.12.2016 17:37
    -4

    В чем неожиданность то? В том что документацию хорошо бы хотя бы читать?


    1. sentyaev
      23.12.2016 23:14
      -4

      Ну что вы в самом деле? Читать доку, писать тесты, знать алгоритмы — это для слабаков.
      Только if (boolVar.ToString().Length == 4), только хардкор.


  1. yogurt1
    23.12.2016 17:37
    -13

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


    1. sentyaev
      23.12.2016 20:40
      +8

      Вы серьезно? Как вообще можно разрабатывать под какую-нибудь платформу, не зная документации?

      Очень легко. Читаем Getting started, кодим. Если не получается читаем StackOverflow, кодим дальше.
      И только спустя какое-то время начинаем читать доку.
      Но обычно пишем на SO вопросы или находим новый модный фрейворк и все сначала.



      1. olexandr17
        24.12.2016 23:27

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


        1. ckr
          25.12.2016 01:25
          +2

          Flash тоже не святой. столько нервов отнял не только у web-мастеров, но и у пользователей.
          В смерти flash вините соцсети. Сколько раз из-за него только мой системник вырубался от перегрева.
          Еще помню как со злости проломил динамик в microlab pro 1 (правда, мне было не жалко, они все равно хрипели).


    1. Dimly
      24.12.2016 20:35

      У меня была дискуссия с руководителем — кем является программист, творцом или ремесленником.
      Вы убедили меня в том что некоторые — все таки ремесленники. Открыть мануал, повторить что написано, скомпоновать по своему — это обычный «кодинг», и даже в этом случае нет гарантий что вы давным давно прочитав описание неких методов — запомнили это и теперь используете. Вероятнее — вы помните что читали, и не читаете повторно, но забыли о чем и не используете.
      Ведь согласитесь вы не перечитываете мануалы при апдейтах? А ведь алгоритм встроенных методом мог изменится, и тот что вы не использовали ранее из-за тормозов, стал оптимальнее текущих ваших приемов.

      Буквально вчера читал даже не описание, а подсказку к настройке плагина Atlassian, где написано «в данной версии умеем присваивать только цифры и текст». Они не указали «списки выбора», и если бы я поверил — то не стал бы использовать для присвоения enumeration, которые внутри все таки цифры, но пойди их еще найди.


  1. Shadow_ru
    23.12.2016 18:42
    +1

    querystring сконвертит все что найдет в строках urlencoded. Сомнительный хак.


  1. ChALkeRx
    23.12.2016 18:44
    +2

    process.on('uncaughtException', console.error);

    Не делайте так. Тем более для «предотвращения остановок сервера».


    Ссылка на доку: https://nodejs.org/dist/latest-v7.x/docs/api/process.html#process_event_uncaughtexception


    конечно, нахожусь в здравом уме, поэтому пользуюсь PM2

    PM2 в вакууме тоже не является гарантией того, что всё это не упадёт. В продакшне надо использовать системные менеджеры для запуска всего, независимо от наличия PM2 (который может использоваться для других целей).


    у объектов EventEmitter имеется и метод code

    code? Это ошибка?


    1. zekohina
      23.12.2016 22:49

      В продакшне надо использовать системные менеджеры для запуска всего

      Можно подробнее? Что это за менеджеры?


      1. ChALkeRx
        23.12.2016 23:43
        +2

        На совеременных Linux-дистрибутивах это systemd, см. http://0pointer.de/blog/projects/watchdog.html.
        На более старой убунте и ещё нескольких дистрибутивах это upstart.
        На чём-то ещё — другие системные средства, которые гарантируют работу сервисов.


        Полагаться на то, что запущенный без какого-либо надсмотрщика pm2/forever/чтоещё сам не упадёт — довольно безумно в продакшне.


        1. bobelev
          24.12.2016 08:59
          +1

          PM2 в последних релизах сделал интеграцию с systemd/upstart, так что теперь чуточку удобнее.


          1. ChALkeRx
            24.12.2016 16:12

            Ага, спасибо, я взгляну на это чуть позже, интересно, как это реализовано и поддерживают ли они watchdog.


            Когда я последний раз смотрел, у pm2 была целая куча жалоб в issues где пользователи запускали его в отрыве от всего а потом ловили внезапный oom, который убивал pm2 и очень удивлялись.


            По-хорошему, таким утилитам нужна не только опциональная поддержка интеграции, но ещё и активное напоминание о том, что её всё-таки стоит включить (если его там ещё нет).


            Сейчас у меня довольно плохо всё со временем, но я себе записал в список дел посмотреть на то, как именно в pm2 организовали интеграцию с systemd/upstart и прокомментировать, если я замечу в этом косяки.


  1. izonder
    23.12.2016 18:45
    +2

    Касательно п.5: будьте осторожны, без __dirname иногда не обойтись, например, когда ваш компонент подключается как npm-модуль, в данном случае require справится, а вот fs.readFile — нет (старый добрый ENOENT).


    1. RuGrof
      23.12.2016 22:49
      +2

      Практически везде, кроме require, относительный путь берётся от директории запуска.
      Если вы запускается скрипт из /~ или C:\ то все пути fs.readFile('./...') будут идти от этой директории, в то время как в require путь берётся от файла, где он вызван.


    1. Disbeleiver
      24.12.2016 08:03
      +1

      На текущую директорию в серверном приложении вообще полагаться нельзя (разве что если вы временные файлы создаете, да и для тех tmpfs есть). Она зависит от того, какой командой и каким обработчиком запускается приложение, и совершенно не факт, что она совпадет с директорией, в которой лежат ваши файлы.В зависимости от фантазии админа это можент быть как ~ или /root/, так и /etc/cron.d/ или F:/MUSTDIE.


  1. Aingis
    23.12.2016 20:08

    4. Когда Node запускается в режиме REPL, модули загружаются автоматически (точнее – по запросу). Например, можно просто ввести в командной строке os.arch() для того, чтобы узнать архитектуру ОС. Конструкция вроде require(`os`).arch(); не нужна.
    Системные модули подгружаются, а вот, скажем, lodash нет. Хуже того, если сделать require('lodash'), то после выполнения следующей команды модуль куда-то пропадает.


    1. ChALkeRx
      23.12.2016 20:32
      +1

      В смысле — пропадает? Покажите конкретную последовательность команд, пожалуйста.


    1. Mithgol
      24.12.2016 02:40
      +3

      Если сделать просто «require('lodash')», но не присвоить присвоить результат «require('lodash')» в явном виде той переменной, имя которой состоит из знака подчёркивания (такое имя переменной чаще всего и используется пользователями lodash), то тогда этот результат присвоится этой переменной автоматически, но не менее автоматически пропадёт (то есть переменная эта переприсвоится) на следующем же шаге REPL — оттого, что в REPL переменная с таким именем имеет особый смысл, что также сказано в документации Node.js.


  1. rayz_razko
    23.12.2016 20:50
    +2

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


    1. ChALkeRx
      23.12.2016 20:56
      +1

      Я помню как один раз у нас в документации <<<<<<< HEAD нашёлся, вот это было неожиданно.


      Но с тех пор добавилась проверка на них на этапе линта на CI.


      1. Alex_Crack
        23.12.2016 22:49

        Я запускаю проект так:
        NODE_PATH=. node app.js
        и все модули ищутся относительно текущей директории. Соответсвенно в продакшне используется pm2 и для него создается список процессов processes.json, где также задается эта переменная окружения.


        1. ChALkeRx
          23.12.2016 23:47
          +1

          Не совсем понял, как это относится к моему комментарию выше, но ладно.


          Так же я не понял, зачем вам нужен NODE_PATH, и почему вы не кладёте зависимости в папку приложения.
          Лучше всего NODE_PATH не пользоваться вообще и забыть про него, он оставлен для совместимости.


        1. ckr
          24.12.2016 03:40
          +1

          Переопределение NODE_PATH на корень своего проекта — совсем не тру-практика.
          Здравым смыслом подразумевается, что в переменной NODE_PATH хранится корень установленного NodeJS.


          1. ChALkeRx
            24.12.2016 16:16
            +1

            Здравым смыслом подразумевается, что в переменной NODE_PATH хранится корень установленного NodeJS.

            Если честно, я не понял мысли. Можете показать на примере путей, что вы имеете ввиду?


            К слову о «тру-практиках» — использование NODE_PATH вообще не рекомендовано.


            1. ckr
              24.12.2016 21:40

              По умолчанию при установке NodeJS переменная NODE_PATH не задается в окружение. Оставить ее пустой или незаданной гораздо лучше, чем использовать не по назначению. Я могу и ошибаться. Но настраивать NODE_PATH в корень одного проекта — это может быть уместно если на одной машине один проект и, соответственно, запускается один app.js.

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


              1. ckr
                24.12.2016 21:50

                Скорее всего, по поводу тру- или нетру- практики я перегнул палку. Тут нет однозначного ответа. В продакшене, обычно переменные, именуемые КАПСом у меня имеют одно значение для всех скриптов, заданное в profile при загрузки ОС. Тут Node не одинок. Среди переменных есть GOPATH, ROR_PATH…


    1. safecode
      01.01.2017 15:23

      Вещи, может, очевидные, а статья полезная, т.к. не всегда вдумываешься в то, что написано. Смотришь только на параметры. К примеру, я привык на PHP к тому, что array_slice возвращает часть массива, и эту часть можно изменять. А в Node.js нельзя изменять часть буфера, если не нужно менять оригинал.


  1. koutsenko
    23.12.2016 21:46
    +1

    Раз речь зашла о системных мелочах Node.js.

    Хотел бы спросить.
    Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?
    Ограничение «только относительные пути» ухудшает читабельность и рефакторинг.
    Насчет упаковки включаемого кода в node_modules в курсе — не выход (неудобно).


    1. Zenitchik
      23.12.2016 22:12
      +1

      Я правильно понял, что у вас есть файл moduleB.js и из него вы хотите подключить файл, расположенный по отношению к нему по пути ../../../../moduleA.js и чтобы было как-то типа require(«moduleA»)?
      Я что-то такое делал, сейчас поищу.


      1. Zenitchik
        23.12.2016 22:20

        Можно добавить в module.paths путей папок, в которых будет искаться модуль. Выведите в консоль, что у Вас содержится в этом свойстве и сможете придумать какую логику над этим надо провернуть.


        1. koutsenko
          23.12.2016 22:58

          Значение module.paths вычисляемое и различается для модулей в разных каталогах…

          Вообще require поддерживает абсолютные пути, но относительно корня текущего диска.
          Может быть стоит где-нить в main.js кроссплатформенно вычислить абсолютный путь до корня проекта.
          Записать его в глоб. переменную (одна переменная — терпимо) и дальше везде тупо собирать полный путь…


          1. Zenitchik
            23.12.2016 23:01

            Путь вида «корень-проекта/node_modules» наверняка уже содержится в module.paths. Опознать его и обрезать node_modules, я думаю, не слишком сложно.


            1. koutsenko
              23.12.2016 23:18

              Кажется, хватит тупого сохранения значения __dirname в main.js.

              Прокатит даже для клиентского js-кода где за require-кухню отвечает webpack.
              Только придется поддержку __dirname ему в конфиге включить явно.


            1. 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 — нормально


              1. ckr
                24.12.2016 05:46

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


              1. iShatokhin
                25.12.2016 00:07

                А зачем засорять global? Можно и так:


                var module = require(path.resolve(process.cwd(), 'controller/net/moduleA'));


            1. koutsenko
              24.12.2016 03:23

              +можно упростить — вместо path экспортировать сразу path.resolve =)


      1. koutsenko
        23.12.2016 22:44

        Да, верно… Для разового включения это терпимо.
        Но допустим, этот moduleA.js включен много где в файлах расположенных разных по уровню каталогах.
        Каждый раз приходится «спускаться» до корня проекта вручную, потом «подниматься» до модуля.

        Либо задумываться о замене CommonJS-вского require на что-то более умное.
        Либо делать костыли — заранее в main.js подключить всё и сделать глобальным (с неймспейсами если надо):
        global.ModuleA = require('./moduleA.js')


        1. 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')?


    1. justboris
      23.12.2016 23:57

      1) Посмотрите на NODE_PATH


      2) Но вообще, если у вас есть такие длинные пути в require, то почему бы просто не перенести зависимые модули в папки поближе друг другу?


      1. koutsenko
        24.12.2016 03:15

        1) Вариант если пути к модулям неизменны и их мало. Слово «модуль» в контексте «файл с js-кодом».
        2) Вариант если нет фреймворка или иерархии компонент в сочетании например с Redux

        Впрочем, пока для себя определился.


    1. mamiamradio
      24.12.2016 12:51

      Посмотрите npm пакет inpack


    1. gearbox
      24.12.2016 16:39
      +1

      >Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?

      можно начать собирать webpack-ом, там резолвинг настраивается. А можно начать использовать typescript — module resolution. В общем инстументы есть.


      1. koutsenko
        24.12.2016 21:52
        +1

        А webpack для сервера зачем? Пост про ноду…


        1. justboris
          25.12.2016 01:42
          +1

          Чтобы собрать модули по своей кастомной логике, если не устраивает стандартная нодовская.


      1. koutsenko
        25.12.2016 00:36

        Спасибо за инфу про резолвинг! Проверил, на клиенте вопрос с путями в require решен без костылей.


        1. gearbox
          27.12.2016 18:31

          you are welcome! )


  1. le1ic
    24.12.2016 00:30
    +1

    Код в п.5 делает совершенно разные вещи и невзаимозаменяем.
    п6 есть почти в любом языке программирования, как можно этого не знать.
    пп 9,11,12 — все про одно и тоже и тоже довольно очевидно (сюрпризом было бы отсутствие констант) для всех, кто хоть раз читал описание любого нормального API.


    1. to0n1
      24.12.2016 02:09

      можете по п5 подробнее?
      с остальным согласен более чем полностью!


      1. ckr
        24.12.2016 03:09
        +3

        Относительные пути работают не только с модулем fs.Тут все гораздо более глобально. Это относится не столько к NodeJS, сколько к основам принципов работы операционных систем.


      1. le1ic
        24.12.2016 14:08
        +1

        Первый код считает путь начиная месторасположения текущего файла. Второй код считает от current working directory текущего процесса, что в общем случае директория откуда выполнен запуск.
        https://nodejs.org/docs/latest/api/globals.html#globals_dirname


  1. 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-код. Ну, и нельзя отметать возможность отлаживать взаимодействие с помощью браузера.


  1. ckr
    24.12.2016 03:31
    +1

    Хочется также поздравить автора с эволюционированием скилла разработчика до продвинутого разработчика.
    Внимательное изучение API-документации используемых средств разработки происходит далеко не в самом начале карьеры.
    Следующий этап развития разработчика (назовем его, например, гуру разработчик) происходит, когда разработчик изучает используемые средства по исходным кодам этих средств.
    Тут я серьезно! Когда все API-документации средств изучены, тяга к познаниям обязательно заставит изучать используемые в разработке средства по исходным кодам.


  1. ckr
    24.12.2016 03:50

    В добавление к п.2 хочется отметить наличие readline-интерфейса. Тоже неплохая альтернатива отладки без использования браузера.


    1. ckr
      24.12.2016 22:42

      Посмотрел в документацию и сам чуть не поседел. Столько всего нового, что вообще не понятно, что я имел в виду, когда писал комментарий выше.

      NodeJS. как и многие другие инструментальные средства по Unix-традициям запускается тремя способами:

      1. путь к файлу скрипта предается параметром node script.js;
      2. скрипт передается процессу node через стандартный поток ввода cat script.js | node;
      3. или просто вручную запускаем node без параметров node.


      Ручной запуск ноды без параметров инициализирует readline-интерфейс. Здесь нода будет выполнять скрипт по-строчно с теми же возможностями, как и в консоли браузера.

      Главный файл проекта можно подключить параметром запуска --require или функцией require() или даже import. А после подключения главного модуля, сервер или что там подключали работает, а во время выполнения этого процесса можно его отлаживать, например, проверяя значения переменных или вручную вызывая различные функции, влияющие на состояние процесса.


  1. Mingun
    24.12.2016 13:29
    +2

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

    Вообще-то «backticks» — это обратные кавычки. Одинарные — это «single quotes» (')


    1. ckr
      24.12.2016 20:22

      Вот дела, а я сам и не заметил сразу).

      Ничего не имею против es6-шаблонов, определенных таким образом. Но использовать их вместо статических строк не рационально. Автору советую лучше протестировать производительность шаблонов и статических строк. Интересно, как помогает автору pm2 в отладке?! Автор, какие средства используете для борьбы с утечками?


      1. ChALkeRx
        25.12.2016 00:23
        +2

        Интересно, как помогает автору pm2 в отладке?! Автор, какие средства используете для борьбы с утечками?

        Автора в этой теме нет, вы говорите с переводом на русский язык, размещённом в блоге хостера VDS/VPS.


  1. superconductor
    24.12.2016 20:34
    +1

    <зануда mode>
    Backticks — это все-таки апострофы, а не одинарные кавычки.
    </зануда mode>
    Странно сравнивать производительность net и http, учитывая, что второй должен не просто данные собрать, но и http распарсить.
    Если я ничего не путают http использует net внутри для собственно работы с сетью.
    И я так и не понял, почему 5500/3400 — это "вдвое быстрее".


  1. Klestofer
    24.12.2016 20:34

    Небольшие опечатки в совете 12: http.STATUS_CODES


    1. ru_vds
      27.12.2016 14:31

      Исправлено. Спасибо за замечание.


  1. yarkov
    24.12.2016 20:34
    +1

    node --inspect — то чего так не хватало. Спасибо огромное от всей души.


  1. 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'
    

    Очень удобный способ форматирования строк, на мой взгляд.


  1. 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
    


    Такие ошибки запутывают новичков и приучают работать с абсолютными путями)))))


  1. bubuq
    27.12.2016 15:46
    +1

    require(`net`).isIP(`cats`)
    

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

    Мы заметили, что вы используете не одинарные кавычки, а обратные апострофы.
    Было бы оправдано, если бы вы использовали шаблоны:


    require('net').isIP(`${ myPet }s`)

    но вы ведь нет, так что зачем?


    1. justboris
      27.12.2016 23:09
      +1

      Про одинарные кавычки, это ошибка переводчика, в оригинале было backticks.


      Зачем их использовать даже вне шаблонов? Для единообразия, иногда это удобно, что абсолютно все строки оформляются одинаковыми значками. Вот тут есть больше слов об этом.