19 неожиданных находок в документации Node.js

https://hackernoon.com/19-things-i-learnt-reading-the-nodejs-docs-8a2dcc7f307f#.xilpsd1so
  • Перевод
Мне хотелось бы думать, что я вполне прилично знаю 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? Если так – делитесь :)

RUVDS.com

928,00

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией

Похожие публикации

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

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

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

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

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

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

            Очень легко. Читаем Getting started, кодим. Если не получается читаем StackOverflow, кодим дальше.
            И только спустя какое-то время начинаем читать доку.
            Но обычно пишем на SO вопросы или находим новый модный фрейворк и все сначала.
              +4
                0
                мне кажется, такой стиль программирования погубил flash.
                потому что, платформа отличная, но низкий порог вхождения все испортил.
                  +2
                  Flash тоже не святой. столько нервов отнял не только у web-мастеров, но и у пользователей.
                  В смерти flash вините соцсети. Сколько раз из-за него только мой системник вырубался от перегрева.
                  Еще помню как со злости проломил динамик в microlab pro 1 (правда, мне было не жалко, они все равно хрипели).
                0
                У меня была дискуссия с руководителем — кем является программист, творцом или ремесленником.
                Вы убедили меня в том что некоторые — все таки ремесленники. Открыть мануал, повторить что написано, скомпоновать по своему — это обычный «кодинг», и даже в этом случае нет гарантий что вы давным давно прочитав описание неких методов — запомнили это и теперь используете. Вероятнее — вы помните что читали, и не читаете повторно, но забыли о чем и не используете.
                Ведь согласитесь вы не перечитываете мануалы при апдейтах? А ведь алгоритм встроенных методом мог изменится, и тот что вы не использовали ранее из-за тормозов, стал оптимальнее текущих ваших приемов.

                Буквально вчера читал даже не описание, а подсказку к настройке плагина Atlassian, где написано «в данной версии умеем присваивать только цифры и текст». Они не указали «списки выбора», и если бы я поверил — то не стал бы использовать для присвоения enumeration, которые внутри все таки цифры, но пойди их еще найди.
                +1
                querystring сконвертит все что найдет в строках urlencoded. Сомнительный хак.
                  +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? Это ошибка?

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

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

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


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

                        +1

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

                          0

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


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


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


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

                    +2
                    Касательно п.5: будьте осторожны, без __dirname иногда не обойтись, например, когда ваш компонент подключается как npm-модуль, в данном случае require справится, а вот fs.readFile — нет (старый добрый ENOENT).
                      +2
                      Практически везде, кроме require, относительный путь берётся от директории запуска.
                      Если вы запускается скрипт из /~ или C:\ то все пути fs.readFile('./...') будут идти от этой директории, в то время как в require путь берётся от файла, где он вызван.
                        +1
                        На текущую директорию в серверном приложении вообще полагаться нельзя (разве что если вы временные файлы создаете, да и для тех tmpfs есть). Она зависит от того, какой командой и каким обработчиком запускается приложение, и совершенно не факт, что она совпадет с директорией, в которой лежат ваши файлы.В зависимости от фантазии админа это можент быть как ~ или /root/, так и /etc/cron.d/ или F:/MUSTDIE.
                        0
                        4. Когда Node запускается в режиме REPL, модули загружаются автоматически (точнее – по запросу). Например, можно просто ввести в командной строке os.arch() для того, чтобы узнать архитектуру ОС. Конструкция вроде require(`os`).arch(); не нужна.
                        Системные модули подгружаются, а вот, скажем, lodash нет. Хуже того, если сделать require('lodash'), то после выполнения следующей команды модуль куда-то пропадает.
                          +1

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

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

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


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

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

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


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

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

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


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

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

                                        В любом случае, вопрос развертывания к сисадмину. Разрабатывать на локальной машине проекты можно так, как удобно разработчику.
                                          0
                                          Скорее всего, по поводу тру- или нетру- практики я перегнул палку. Тут нет однозначного ответа. В продакшене, обычно переменные, именуемые КАПСом у меня имеют одно значение для всех скриптов, заданное в profile при загрузки ОС. Тут Node не одинок. Среди переменных есть GOPATH, ROR_PATH…
                                  0
                                  Вещи, может, очевидные, а статья полезная, т.к. не всегда вдумываешься в то, что написано. Смотришь только на параметры. К примеру, я привык на PHP к тому, что array_slice возвращает часть массива, и эту часть можно изменять. А в Node.js нельзя изменять часть буфера, если не нужно менять оригинал.
                                    +1
                                    Buffer это не Array, и работает по другому и для других целей
                                    Он даже создается таким образом, чтобы показать что тут память вылеляется, а не типичный объект создается
                                    const buf1 = Buffer.alloc(10);

                                    И нужно с понимать что вы делаете

                                    Для всего остального все работает как и ожидается
                                    
                                    let arr = [0, 1, 2, 3]
                                    let arr2 = arr.slice(1)
                                    // arr2 новый массив, изменяя который первый не будет изменен
                                    
                                    let str = "abcdef"
                                    let str2 = str.slice(1, -1)
                                    // str2 новая строка, независимая от первой
                                    
                                    //Если вам нужна просто копия всего, можно сделать
                                    let copyArr = arr.slice()
                                    
                                  +1
                                  Раз речь зашла о системных мелочах Node.js.

                                  Хотел бы спросить.
                                  Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?
                                  Ограничение «только относительные пути» ухудшает читабельность и рефакторинг.
                                  Насчет упаковки включаемого кода в node_modules в курсе — не выход (неудобно).
                                    +1
                                    Я правильно понял, что у вас есть файл moduleB.js и из него вы хотите подключить файл, расположенный по отношению к нему по пути ../../../../moduleA.js и чтобы было как-то типа require(«moduleA»)?
                                    Я что-то такое делал, сейчас поищу.
                                      0
                                      Можно добавить в module.paths путей папок, в которых будет искаться модуль. Выведите в консоль, что у Вас содержится в этом свойстве и сможете придумать какую логику над этим надо провернуть.
                                        0
                                        Значение module.paths вычисляемое и различается для модулей в разных каталогах…

                                        Вообще require поддерживает абсолютные пути, но относительно корня текущего диска.
                                        Может быть стоит где-нить в main.js кроссплатформенно вычислить абсолютный путь до корня проекта.
                                        Записать его в глоб. переменную (одна переменная — терпимо) и дальше везде тупо собирать полный путь…
                                          0
                                          Путь вида «корень-проекта/node_modules» наверняка уже содержится в module.paths. Опознать его и обрезать node_modules, я думаю, не слишком сложно.
                                            0
                                            Кажется, хватит тупого сохранения значения __dirname в main.js.

                                            Прокатит даже для клиентского js-кода где за require-кухню отвечает webpack.
                                            Только придется поддержку __dirname ему в конфиге включить явно.
                                              0
                                              итого, в точке входа в корне проекта пишу
                                              global.path = require('path');
                                              global.root = __dirname;
                                              


                                              инклуды в других файлах, было:
                                              var module = require('../../../../moduleA');
                                              


                                              стало:
                                              var module = require(path.resolve(root, 'controller/net/moduleA'));
                                              


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

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


                                                  var module = require(path.resolve(process.cwd(), 'controller/net/moduleA'));
                                                  0
                                                  +можно упростить — вместо path экспортировать сразу path.resolve =)
                                              0
                                              Да, верно… Для разового включения это терпимо.
                                              Но допустим, этот moduleA.js включен много где в файлах расположенных разных по уровню каталогах.
                                              Каждый раз приходится «спускаться» до корня проекта вручную, потом «подниматься» до модуля.

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

                                                	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')?
                                              0

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


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

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

                                                Впрочем, пока для себя определился.
                                                0
                                                Посмотрите npm пакет inpack
                                                  +1
                                                  >Может уже придумали решение чтобы не делать require('../../../../moduleA') внутри moduleB?

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

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

                                                      0
                                                      Спасибо за инфу про резолвинг! Проверил, на клиенте вопрос с путями в require решен без костылей.
                                                        0
                                                        you are welcome! )
                                                    +1
                                                    Код в п.5 делает совершенно разные вещи и невзаимозаменяем.
                                                    п6 есть почти в любом языке программирования, как можно этого не знать.
                                                    пп 9,11,12 — все про одно и тоже и тоже довольно очевидно (сюрпризом было бы отсутствие констант) для всех, кто хоть раз читал описание любого нормального API.
                                                      0
                                                      можете по п5 подробнее?
                                                      с остальным согласен более чем полностью!
                                                        +3
                                                        Относительные пути работают не только с модулем fs.Тут все гораздо более глобально. Это относится не столько к NodeJS, сколько к основам принципов работы операционных систем.
                                                          +1
                                                          Первый код считает путь начиная месторасположения текущего файла. Второй код считает от current working directory текущего процесса, что в общем случае директория откуда выполнен запуск.
                                                          https://nodejs.org/docs/latest/api/globals.html#globals_dirname
                                                        +1
                                                        Модуль net вдвое быстрее модуля http


                                                        Спорное утверждение. Это как сравнивать теплое с мягким. Я бы утверждал что скорость их работы при заданных условиях работы приблизительно одинаковая.

                                                        В результате http.Server смог обработать примерно 3400 запросов в секунду, а net.Server – примерно 5500. К тому же, net.Server проще устроен.


                                                        Ваша статистическое утверждение никак не может быть объективным. Во-первых, вы пренебрегаете keep-alive-соединениями. Во-вторых, количеством одновременных соединений.

                                                        Конечно же, используя net можно написать реализацию любого протокола. Но, в большинстве случаев отладка своего протокола может превратиться в ад. Для большинства случаев подходит простенький и экстремально быстрый пакет ws, основанный на WebSocket. Удобно это прежде всего тем, что двухсторонний обмен данными происходит сообщениями из js-объектов. Это позволяет не заморочиваться с проектированием протокола обмена, можно добавлять и удалять параметры в сообщения и их обработку в любой момент разработки, что повысит гибкость работы с версиями API. Также, если не ошибаюсь, через объект этого сообщения можно передавать функции, содержащие произвольный js-код. Ну, и нельзя отметать возможность отлаживать взаимодействие с помощью браузера.
                                                          +1
                                                          Хочется также поздравить автора с эволюционированием скилла разработчика до продвинутого разработчика.
                                                          Внимательное изучение API-документации используемых средств разработки происходит далеко не в самом начале карьеры.
                                                          Следующий этап развития разработчика (назовем его, например, гуру разработчик) происходит, когда разработчик изучает используемые средства по исходным кодам этих средств.
                                                          Тут я серьезно! Когда все API-документации средств изучены, тяга к познаниям обязательно заставит изучать используемые в разработке средства по исходным кодам.
                                                            +1
                                                            Внимательное изучение API-документации используемых средств разработки происходит далеко не в самом начале карьеры.

                                                            А я изучал на первом курсе справочную и методологическую часть API платформ по книгам, начиная со второго-третьего — с официальных документаций API, с третьего-четвертого — с исходных кодов.
                                                            С тех пор придерживаюсь этого стиля.
                                                            При изучении новых платформ/новых версий давно знаю, какие ключевые точки посмотреть для изучения — для быстрого входа в разработку, далее смотрю документацию итеративно, +ресурсы типа забра помогают.

                                                            И надо сказать, что зная точно, какую часть API для каких задач и как именно использовать, тяжело смотреть на код коллег, «кодящих» по подсказкам IntelliSense — такой подход часто приводит к использованию методов не так, как они предназначены для решения задачи (отличия могут быть в параметрах, обработке исключений), к использованию неподходящего метода, к использованию методов, ставших Obsolete N версий назад, или и вообще к своему велосипедному коду, в то время, когда в платформе с версии 1.0 есть стандартные методы для решения задачи.

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

                                                            В свое время столкнулся с реальным случаем, когда в продукте, развивавшимся 10 лет, разработчики сталкивались с падениями OutOfMemory exception, т.к. в своем велосипедном ORM кодировали запросах BLOB'ы в виде шестнадцатеричном текстовом виде.
                                                            Т.е., за 10 лет никто не открыл ни одной книжки, ни официальной справки (офлайновая в IDE, веб на официальном сайте), и не узнал, что параметризованные SQL запросы существовали в платформе с версии 1.0
                                                            (тем более не говорю про возможность потокового чтения-записи блоб'ов в БД).
                                                            0
                                                            В добавление к п.2 хочется отметить наличие readline-интерфейса. Тоже неплохая альтернатива отладки без использования браузера.
                                                              0
                                                              Посмотрел в документацию и сам чуть не поседел. Столько всего нового, что вообще не понятно, что я имел в виду, когда писал комментарий выше.

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


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

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

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

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

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

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

                                                                +1

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

                                                                  0
                                                                  Небольшие опечатки в совете 12: http.STATUS_CODES
                                                                    0
                                                                    Исправлено. Спасибо за замечание.
                                                                    +1
                                                                    node --inspect — то чего так не хватало. Спасибо огромное от всей души.
                                                                      +2
                                                                      Тут выше писали о том, что backtick — это не просто одинарная кавычка. Вставлю свои 5 копеек по поводу удобства использования именно ` вместо " и '. Можно делать вот так:
                                                                      let a = 10;
                                                                      let b = 15;
                                                                      console.log(`Сумма: ${a+b}`); // Выведет Сумма: 25
                                                                      // Внутрь ${} можно подставлять любое js-выражение
                                                                      var str = `Логарифм: ${Math.log(10)}`); 
                                                                      //str == 'Логарифм: 2.302585092994046'
                                                                      

                                                                      Очень удобный способ форматирования строк, на мой взгляд.
                                                                        0
                                                                        Поправьте пожалуйста пункт 5:
                                                                        __dirname не имеет отношение к cwd, __dirname — это путь к папке с файлом, в котором она написана.

                                                                        scripts/test.js
                                                                        console.log(__dirname)
                                                                        


                                                                        $ node scripts/test.js 
                                                                        /path/to/your/project/scripts
                                                                        


                                                                        Такие ошибки запутывают новичков и приучают работать с абсолютными путями)))))
                                                                          +1
                                                                          require(`net`).isIP(`cats`)
                                                                          

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

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


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

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

                                                                            +1

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


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

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

                                                                          Самое читаемое