Node.js — HackerX https://hackerx.ru Блог о программировании, СУБД, сетях и алгоритмах. Уроки и статьи по разным языкам программирования Wed, 11 Jul 2018 07:15:23 +0000 ru-RU hourly 1 https://wordpress.org/?v=4.9.8 Сжатие / восстановление данных с использованием библиотеки Zlib https://hackerx.ru/node-data-compression-library-zlib/ https://hackerx.ru/node-data-compression-library-zlib/#respond Sun, 08 Jul 2018 14:15:08 +0000 https://hackerx.ru/?p=999 Модуль Zlib предоставляет функциональность сжатия/восстановления данных. В его основу заложен поток преобразования данных, что становится очевидным после знакомства с примером сжатия файла, приведенным в документации Node. Я слегка изменила этот пример для работы с большим файлом. const zlib = require('zlib'); const fs = require('fs'); const gzip = zlib.createGzip(); const inp = fs.createReadStream('test.png'); var out = […]

The post Сжатие / восстановление данных с использованием библиотеки Zlib appeared first on HackerX.

]]>
Модуль Zlib предоставляет функциональность сжатия/восстановления данных. В его основу заложен поток преобразования данных, что становится очевидным после знакомства с примером сжатия файла, приведенным в документации Node. Я слегка изменила этот пример для работы с большим файлом.

const zlib = require('zlib');
const fs = require('fs');

const gzip = zlib.createGzip();

const inp = fs.createReadStream('test.png');
var out = fs.createWriteStream('test.png.gz');

inp.pipe(gzip).pipe(out);

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

Zlib предоставляет поддержку сжатия zlib и deflate — более сложного и управляемого алгоритма сжатия. Если файлы, созданные с применением zlib, могут быть распакованы программой командной строки gunzip (или unzip), у файлов, созданных с применением deflate, такая возможность отсутствует. Для восстановления файла, сжатого в режиме deflate, придется использовать Node или другую функциональность.

Чтобы продемонстрировать функциональность сжатия и восстановления файлов, мы создадим две программы командной строки: compress и uncompress.

Первая программа сжимает файл с выбором алгоритма gzip или deflate в параметрах командной строки. Так как мы собираемся работать с параметрами командной строки, для них также следует включить модуль Commander:

const zlib = require('zlib');
const program = require('commander');
const fs = require('fs');

program
  .version ('0.0.1')
  .option ('-s, --source [file name]', 'Source File Name')
  .option ('-f, --file [file name]', 'Destination File name')
  .option ('-t, --type <mode>', /^(gzip|deflate)$/i)
  .parse(process.argv);

var compress;
if (program.type == 'deflate')
  compress = zlib.createDeflate();
else
  compress = zlib.createGzip();

var inp = fs.createReadStream(program.source);
var out = fs.createWriteStream(program.file);

inp.pipe(compress).pipe(out);

Такие приложения интересны и полезны (особенно в среде Windows, в которой нет встроенной реализации сжатия), но технология сжатия особенно популярна в веб-запросах. Документация Node содержит примеры функциональности Zlib с веб-запросами.

Вместо получения сжатых файлов я покажу, как отправить сжатый файл на сервер, где он затем будет восстановлен. Сейчас мы создадим клиент и сервер для сжатия большого файла PNG и отправки его с запросом HTTP. Далее сервер будет восстановить данные и сохраняет файл.



Обратите внимание: передаваемые данные читаются в массив фрагментов, который затем используется для создания нового объекта Buffer вызовом buffer.concat(). Так как мы работаем с буфером, а не с потоком, использовать функцию pipe() не удастся. Вместо этого я буду использовать вспомогательную функцию Zlib zlib.unzip, которой передается объект Buffer и функция обратного вызова. Аргументы функции обратного вызова содержат ошибку и результат. Результат также представляет собой объект Buffer, который записывается во вновь созданный поток для записи функцией write(). Чтобы программа создавала разные экземпляры файла, имя файла дополняется временной меткой.

Создание веб-сервера, который получает сжатые данные и распаковывает их в файл:

const http = require('http');
const zlib = require('zlib');
const fs = require('fs');

const server = http.createServer().listen(8080);

server.on('request', function(request,response) {
  if (request.method == 'POST') {
    var chunks = [];

    request.on('data', function(chunk) {
      chunks.push(chunk);
    });

    request.on('end', function() {
      var buf = Buffer.concat(chunks);
      zlib.unzip(buf, function(err, result) {
        if (err) {
          response.writeHead(500);
          response.end();
          return console.log('error ' + err);
        }
        var timestamp = Date.now();
        var filename = './done' + timestamp + '.png';
        fs.createWriteStream(filename).write(result);
      });
      response.writeHead(200, {'Content-Type': 'text/plain'});
      response.end('Received and undecompressed file\n');
    });
  }
});

console.log('server listening on 8080');

Ключевой момент клиентского кода в листинге — назначение правильной кодировки Content-Encoding в заголовке. Заголовок должен содержать значение ‘gzip,deflate’. Также заголовок Content-Type: ‘application/javascript’.

Клиент сжимает файл и передает его с веб-запросом:

const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();

const options = {
  hostname: 'localhost',
  port: 8124,
  method: 'POST',
  headers: {
    'Content-Type': 'application/javascript',
    'Content-Encoding': 'gzip,deflate'
  }
};

const req = http.request(options, function(res) {
  res.setEncoding('utf8');
  var data = '';
  res.on('data', function (chunk) {
    data+=chunk;
  });

  res.on('end', function() {
    console.log(data)
  })
});

req.on('error', function(e) {
  console.log('problem with request: ' + e.message);
});

// Потоковая передача сжатого файла серверу
var readable = fs.createReadStream('./test.png');
readable.pipe(gzip).pipe(req);

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

Буферизация файла в памяти может создать проблемы с масштабированием, поэтому возможно другое решение: сохранить несжатый файл, распаковать его и затем удалить временный несжатый файл. Оставляю его для самостоятельной работы.

The post Сжатие / восстановление данных с использованием библиотеки Zlib appeared first on HackerX.

]]>
https://hackerx.ru/node-data-compression-library-zlib/feed/ 0
Node.js: Потоки (Stream) и pipe() https://hackerx.ru/node-js-stream-pipe/ https://hackerx.ru/node-js-stream-pipe/#respond Sun, 08 Jul 2018 08:45:41 +0000 https://hackerx.ru/?p=989 Потоковая технология проявляется во всех базовых аспектах Node; она предоставляет функциональность для HTTP, а также для других форм сетевой передачи данных. Кроме того, она предоставляет функциональность для файловой системы. Поток представлен абстрактным интерфейсом, это означает, что вы не будете создавать потоки напрямую. Вместо этого вы будете работать с различными объектами, реализующими интерфейс Stream, — запросами […]

The post Node.js: Потоки (Stream) и pipe() appeared first on HackerX.

]]>
Потоковая технология проявляется во всех базовых аспектах Node; она предоставляет функциональность для HTTP, а также для других форм сетевой передачи данных. Кроме того, она предоставляет функциональность для файловой системы.

Поток представлен абстрактным интерфейсом, это означает, что вы не будете создавать потоки напрямую. Вместо этого вы будете работать с различными объектами, реализующими интерфейс Stream, — запросами HTTP, потоками для чтения или записи модуля File System, объектами сжатия Zlib или process.stdout. Непосредственно реализовать Stream API вам придется только в одном случае: при создании собственной реализации потока.

Так как многие объекты в Node реализуют потоковый интерфейс, все потоки в Node обладают базовой функциональностью:

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

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

Поток для чтения начинает работу в приостановленном состоянии; это означает, что никакие данные не будут отправляться до того момента, пока не будет явно выполнена операция чтения ( stream.read() ) или команда возобновления работы потока ( stream.resume() ). Однако используемые нами реализации потоков, такие как поток для чтения модуля File System, переключаются в рабочий режим сразу же при программировании события данных (механизм получения доступа к данным в потоке для чтения). В рабочем режиме данные передаются приложению сразу же при их появлении.



Потоки для чтения поддерживают несколько событий, но на практике нас обычно интересуют три события: data, end и error. Событие data отправляется при получении нового фрагмента данных, готового к использованию, а событие end — при потреблении всех данных. Событие error, естественно, отправляется при возникновении ошибки. Ниже вы увидите в примерах использование модуля File System.

Поток для записи представляет собой приемник, в который передаются (записываются) данные. Среди прослушиваемых событий можно выделить error и событие finish, происходящее при вызове end() и сбросе всех данных. Также встречается событие drain , генерируемое в тот момент, когда попытка записи данных возвращает false.

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

Чтобы лучше понять суть потока преобразования данных, необходимо поближе познакомиться с функциональностью, поддерживаемой всеми потоками: функцией pipe(). Пример ее использования представлен ниже, где поток для чтения напрямую связывал содержимое файла с объектом ответа HTTP:
Создание и перенаправление потока для чтения

const file = fs.createReadStream(pathname);
file.on("open", function() {
  file.pipe(res);
});

Вызов pipe() извлекает данные из файла (поток) и выводит их в объект http.ServerResponse. В документации Node указано, что этот объект реализует интерфейс потока для записи и, как будет показано позднее, fs.createReadStream() возвращает fs.ReadStream — реализацию потока для чтения. В число методов, поддерживаемых потоком для чтения, входит и pipe() с потоком для записи.

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

const gzip = zlib.createGzip();
const fs   = require('fs');
const inp  = fs.createReadStream('input.txt');
const out  = fs.createWriteStream('input.txt.gz');

inp.pipe(gzip).pipe(out);

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

The post Node.js: Потоки (Stream) и pipe() appeared first on HackerX.

]]>
https://hackerx.ru/node-js-stream-pipe/feed/ 0
Node.js — Модуль OS: Работа с операционной системой https://hackerx.ru/node-js-%d0%bc%d0%be%d0%b4%d1%83%d0%bb%d1%8c-os-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%86%d0%b8%d0%be%d0%bd%d0%bd%d0%be%d0%b9-%d1%81%d0%b8%d1%81%d1%82%d0%b5/ https://hackerx.ru/node-js-%d0%bc%d0%be%d0%b4%d1%83%d0%bb%d1%8c-os-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%86%d0%b8%d0%be%d0%bd%d0%bd%d0%be%d0%b9-%d1%81%d0%b8%d1%81%d1%82%d0%b5/#respond Sun, 08 Jul 2018 03:57:08 +0000 https://hackerx.ru/?p=983 Некоторым технологиям удается полностью маскировать различия между операционными системами. В других случаях для того, чтобы справиться со спецификой отдельных ОС, разработчику приходится основательно потрудиться. Node занимает промежуточное положение. Обычно приложения, созданные вами, работают в любых системах. Однако существует ряд функциональных областей, в которых проявляются различия между ОС. Иногда Node справляется с ними хорошо, а иногда […]

The post Node.js — Модуль OS: Работа с операционной системой appeared first on HackerX.

]]>
Некоторым технологиям удается полностью маскировать различия между операционными системами. В других случаях для того, чтобы справиться со спецификой отдельных ОС, разработчику приходится основательно потрудиться. Node занимает промежуточное положение. Обычно приложения, созданные вами, работают в любых системах. Однако существует ряд функциональных областей, в которых проявляются различия между ОС. Иногда Node справляется с ними хорошо, а иногда разработчику приходится прибегать к услугам удобных сторонних модулей.

Для непосредственного получения информации об операционной системе используется базовый модуль OS. Это один из полезных инструментов, упрощающих построение кросс-платформенных приложений. Кроме того, он предоставляет информацию об использовании ресурсов и возможностях текущей среды.

Обращение к модулю OS начинается с команды require, с помощью которого мы подключаем данный модуль:

const os = require('os');

Вся функциональность модуля OS направлена только на получение информации. Например, если вы хотите обеспечить кросс-платформенную поддержку, можно проверить, какой завершитель строки поддерживается текущей системой, использует она прямой (little-endian) или обратный (big endian) порядок байтов, а также узнать временный и домашний каталог:

const os = require('os');

console.log('Using end of line' + os.EOL + 'to insert a new line');
console.log(os.endianness());
console.log(os.tmpdir());
console.log(os.homedir());

На моем сервере с Ubuntu и компьютере с Windows 10 используется прямой порядок байтов, а завершитель строки (EOL, End-Of-Line) в обеих системах работает так, как и следовало ожидать (вторая часть текста начинается с новой строки). Различаются только временный и домашний каталоги, что вполне естественно.



Модуль OS также предоставляет средства для проверки доступных ресурсов текущей машины.

console.log(os.freemem());
console.log(os.loadavg());
console.log(os.totalmem());

Функция os.loadavg() специфична для Unix; в Windows она просто возвращает нули. Она возвращает показатель средней загрузки, отражающий текущую интенсивность работы системы, за 1, 5 и 15 минут. Чтобы получить значения в процентах, умножьте числа на 100.

Функции os.freemen() и os.totalmem() возвращают объем памяти в байтах.

Другая функция, os.cpus(), возвращает информацию о процессорах машины. Возвращается количество миллисекунд, проведенных процессором в разных режимах: user, nice, sys, idle и irq. Если вы не знакомы с этими концепциями: значение user определяет время, проведенное процессором за выполнением процессов пользовательского пространства, idle — время бездействия, а sys — время, проведенное за выполнением системных процессов (режим ядра). Значение nice отражает величину динамической регулировки приоритета, предотвращающей слишком частое его выполнение. Значение irq описывает прерывания — запросы на обслуживание на аппаратном уровне.

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

The post Node.js — Модуль OS: Работа с операционной системой appeared first on HackerX.

]]>
https://hackerx.ru/node-js-%d0%bc%d0%be%d0%b4%d1%83%d0%bb%d1%8c-os-%d1%80%d0%b0%d0%b1%d0%be%d1%82%d0%b0-%d1%81-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%86%d0%b8%d0%be%d0%bd%d0%bd%d0%be%d0%b9-%d1%81%d0%b8%d1%81%d1%82%d0%b5/feed/ 0
Webpack и Node.js: Руководство по использованию https://hackerx.ru/webpack-and-nodejs/ https://hackerx.ru/webpack-and-nodejs/#respond Fri, 06 Jul 2018 11:06:19 +0000 https://hackerx.ru/?p=973 Введение: Что такое Webpack Разрабатывая приложения для платформы Node.js, старайтесь не использовать си­стем модулей, отличных от той, что предлагается по умолчанию. В идеале нужно продолжать писать модули как обычно, используя require() и module.exports, а затем применять инструмент для преобразования кода в пакет, который будет нормально работать в браузере. К счастью, эта задача имеет множество решений, самым […]

The post Webpack и Node.js: Руководство по использованию appeared first on HackerX.

]]>
Введение: Что такое Webpack

Разрабатывая приложения для платформы Node.js, старайтесь не использовать си­стем модулей, отличных от той, что предлагается по умолчанию. В идеале нужно продолжать писать модули как обычно, используя require() и module.exports, а затем применять инструмент для преобразования кода в пакет, который будет нормально работать в браузере. К счастью, эта задача имеет множество решений, самым попу­ярным из которых является Webpack (https://webpack.github.io).

Webpack позволяет создавать модули с использованием соглашений о модулях Node.js, с последующей компиляцией в пакет (единственный JavaScript-файл), со­держащий все необходимое для работы в браузере (в том числе абстрактные функции require()). Такой пакет затем можно включить в веб-страницу, как обычно, и выпол­нить в браузере. Webpack рекурсивно сканирует исходный код, отыскивает ссылки на функцию require(), разрешает их и добавляет нужные модули в пакет.

Установка и знакомство с Webpack

Для быстрой демонстрации волшебства Webpack рассмотрим, как будет выглядит обычный Node.js модуль при использовании этого инст­румента. Во-первых, необходимо установить сам Webpack, что можно сделать с по­мощью следующей простой команды:

npm install webpack -g

Параметр -g указывает npm, что необходимо установить Webpack глобально, чтобы его можно было использовать из консоли, как будет показано ниже.

Далее, создадим новый проект и попробуем написать модуль. Вот так будет выглядеть его реализация для платформы Node.js (файл sayHello.js):

var mustache = require('mustache');
var template = '<h1>Hello <i>{{name}}</i></h1>';
mustache.parse(template);
module.exports.sayHello = function(toWhom) {
  return mustache.render(template, {name: toWhom});
};

Теперь создадим файл main.js, то есть точку входа в код для браузера:

window.addEventListener('load', function(){
  var sayHello = require('./sayHello').sayHello;
  var hello = sayHello('Browser!');
  var body = document.getElementsByTagName("body")[0];
  body.innerHTML = hello;
});

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

Добавим зависимость mustache в проект:

npm install mustache

А теперь волшебное действие. Запустим следующую команду в терминале:

webpack main.js bundle.js

Она скомпилирует модуль main и соберет все необходимые зависимости в один файл bundle.js, готовый к использованию в браузере!



Чтобы быстро проверить работоспособность получившегося пакета, создадим HTML-страницу magic.html со следующим кодом:

<html>
  <head>
    <title>Webpack magic</title>
    <script src="bundle.js"></script>
  </head>
  <body>
  </body>
</html>

Этого достаточно для выполнения кода в браузере. Попробуйте открыть страницу и взгляните сами. Ура!

Преимущества использования Webpack

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

  • Webpack автоматически предоставляет версии многих модулей ядра Node.js, совместимые с браузером. Это означает, что в браузере можно использовать такие модули, как http, assert, events, и многие другие!
  • Если имеется модуль, несовместимый с браузером, его можно исключить из сборки, заменить пустым объектом или другим модулем с альтернативной реа­лизацией, которая совместима с браузером. Это играет решающую роль, и дан­ная возможность будет использоваться в примере ниже;
  • Webpack может генерировать пакеты для различных модулей;
  • Webpack позволяет выполнять дополнительную обработку исходных файлов с помощью сторонних загрузчиков и плагинов. Имеются загрузчики и пла­гины практически для всего, что может понадобиться, от компиляции Cof­feeScript, TypeScript и ES2015 до поддержки загрузки AMD, пакетов Bower (http://bower.io) и Component (http://component.github.io), использующих require(), от минификации до компиляции и упаковки других ресурсов, таких как шаблоны и таблицы стилей;
  • Webpack с легкостью можно вызывать из диспетчеров заданий, таких как Gulp (https://npmjs.com/package/gulp-webpack) и Grunt (https://npmjs.org/package/grunt-webpack);
  • Webpack позволяет управлять и выполнять предварительную обработку ресур­сов проекта, причем не только JavaScript-файлов, но и таблиц стилей, изобра­жений, шрифтов и шаблонов;
  • кроме того, Webpack можно настроить для разделения дерева зависимостей на несколько частей, которые будут загружаться по требованию, когда они пона­добятся браузеру.

Потенциал и гибкость Webpack настолько привлекательны, что многие разработчи­ки начали использовать его даже для управления кода, предназначенного только для выполнения на стороне клиента. Это стало возможным потому, что многие клиентские библиотеки начали по умолчанию поддерживать CommonJS и npm, что открывает но­вые и очень интересные перспективы. Например, можно установить библиотеку jQuery:

npm install jquery

а затем загрузить ее с помощью простой строки кода:

const $ = require('jquery');

Вы будете удивлены, как много клиентских библиотек уже поддерживают CommonJS и Webpack.

The post Webpack и Node.js: Руководство по использованию appeared first on HackerX.

]]>
https://hackerx.ru/webpack-and-nodejs/feed/ 0
Глобальные объекты: Объекты global и process https://hackerx.ru/node-global-objects/ https://hackerx.ru/node-global-objects/#respond Fri, 20 Apr 2018 08:33:13 +0000 https://hackerx.ru/?p=960 Два важнейших объекта в Node — global и process . Объект global немного похож на глобальный объект в браузере, хотя между ними существуют очень серьезные различия. Объект process , напротив, существует только в Node. Объект global В браузере переменная, объявленная на верхнем уровне, объявляется глобально. В Node дело обстоит иначе. Переменная, объявленная в модуле или […]

The post Глобальные объекты: Объекты global и process appeared first on HackerX.

]]>
Два важнейших объекта в Node — global и process . Объект global немного похож на глобальный объект в браузере, хотя между ними существуют очень серьезные различия. Объект process , напротив, существует только в Node.

Объект global

В браузере переменная, объявленная на верхнем уровне, объявляется глобально. В Node дело обстоит иначе. Переменная, объявленная в модуле или приложении Node, не обладает глобальной доступностью; она ограничивается модулем или приложением. Таким образом, можно объявить «глобальную» переменную с именем str в модуле и в приложении, использующем этот модуль, и никакого конфликта не будет.

Для демонстрации создадим простую функцию, которая прибавляет число к базовому значению и возвращает результат (название файла — add2.js). Функция будет создана как библиотека JavaScript для использования в веб странице, а также как модуль для использования в приложении Node. Код объявляет переменную base, присваивает ей значение 2, после чего прибавляет переданное число:

var base = 2;
function addtwo(input) {
  return parseInt(input) + base;
}

Затем создадим очень простой модуль (название файла — addtwo.js), который делает то же самое, но с использованием синтаксиса модуля Node.

var base = 2;
exports.addtwo = function(input) {
  return parseInt(input) + base;
};

А теперь посмотрим, чем различается концепция глобальности в двух разных средах. Файл add2.js используется в веб-странице, которая также объявляет переменную base:

<!DOCTYPE html>
<html>
 <head>
  <script src="add2.js"></script>
  <script>
   var base = 10;
   console.log(addtwo(10));
  </script>
 </head>
 <body>
 </body>
</html>

При обращении к веб-странице в браузере на консоли браузера выводится значение 20 (вместо ожидаемого 12). Дело в том, что все переменные, объявленные за пределами функции в JavaScript, добавляются в один глобальный объект. Объявляя новую переменную с именем base в веб странице, мы переопределяем значение во включенном файле сценария.

Теперь используем модуль addtwo в приложении Node:

var addtwo = require('./addtwo').addtwo;
var base = 10;
console.log(addtwo(base));

В приложении Node результат равен 12. Объявление новой переменной басе в приложении Node никак не повлияло на значение base в модуле, потому что они существуют в разных глобальных пространствах имен.

Устранение общего пространства имен — безусловное усовершенствование, но оно не универсально. Объект global во всех средах предоставляет доступ к глобально доступным объектам и функциям Node , включая объект процесс (см. далее). Чтобы убедиться в этом, просто добавьте следующую команду в файл и запустите приложение. Команда выводит все глобально доступные объекты и функции:

console.log(global);



Объект process

Объект process принадлежит к числу важнейших компонентов среды Node, так как он предоставляет информацию о среде выполнения. Кроме того, через объект process выполняется стандартный ввод/вывод, вы можете корректно завершить приложение Node и даже выдать сигнал при завершении итерации в цикле событий Node.

Сейчас мы рассмотрим получение информации о среде выполнения объекта process, а также исключительно важные средства стандартного ввода/вывода.

Объект process предоставляет доступ к информации как о среде Node, так и о среде выполнения программы. Для получения информации мы воспользуемся параметром командной строки -p , который выполняет сценарий и возвращает полученный результат. Например, для проверки свойства process. versions введите следующую команду:

$ node -p "process.versions"

{ http_parser: ‘2.5.0’,
node: ‘4.2.1’,
v8: ‘4.5.103.35’,
uv: ‘1.7.5’,
zlib: ‘1.2.8’,
ares: ‘1.10.1-DEV’,
icu: ‘56.1’,
modules: ’46’,
openssl: ‘1.0.2d’ }

Команда выводит список различных компонентов Node и зависимостей, включая версии v8, OpenSSL (библиотека, используемая для безопасной передачи данных), собственно Node и т. д.

Свойство process.env предоставляет богатую информацию о том, что Нодье знает о вашей среде разработки:

$ node -p "process.env"

Особенно интересно проанализировать различия между архитектурами (например, Linux и Windows).

Чтобы просмотреть содержимое process.release, введите следующую команду:

$ node -p "process.release"

Полученный результат зависит от того, что установлено на вашем компьютере. И в LTS, и в среде текущей версии будет выведено имя приложения, а также URL исходного кода. Но в LTS также будет выведено дополнительное свойство:

$ node -p «process.release.lts»
‘Argon’

Однако при обращении к тому же значению в текущей версии (например, v6) будет получен другой результат:

$ node -p «process.release.lts»
undefined

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

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

Стандартные потоки представляют собой заранее определенные каналы передачи данных между приложением и средой: стандартный ввод ( stdin ), стандартный вывод ( stdout ) и стандартный поток ошибок ( stderr ). В приложении Node эти каналы обеспечивают взаимодействие между приложением Нодье и терминалом, своего рода механизм прямого «общения» с приложением.

Node поддерживает каналы с тремя функциями process:

  • process.stdin : поток с поддержкой чтения для stdin;
  • process.stdout : поток с поддержкой записи для stdout;
  • process.stderr : поток с поддержкой записи для stderr.

Вы не можете закрывать эти потоки в своем приложении. Поддерживается только чтение данных из канала stdin и запись в каналы stdout и stderr.

Функции ввода/вывода process наследуют от EventEmitter, это означает, что они могут генерировать события, а вы можете перехватывать эти события и обрабатывать любые данные. Чтобы обработать входные данные с использованием process.stdin , прежде всего необходимо назначить кодировку потока. Если этого не сделать, вы получите результаты в виде буфера, а не в виде строки:

process.stdin.setEncoding('utf8');

Затем начинается прослушивание события readable , которое сообщает о поступлении блока данных, готового к чтению. Затем функция process.stdin. read() используется для чтения данных, и если данные отличны от null, они копируются в process.stdout при помощи функции process.stdout.write():

process.stdin.on('readable', function() {
  var input = process.stdin.read();
  if (input !== null) {
    // Эхо-вывод текста
    process.stdout.write(input);
  }
});

В принципе можно пропустить назначение кодировки и получить те же результаты; программа будет читать буфер и выводить буфер, но для пользователя приложения все выглядит так, словно вы работаете с текстом (строка). На самом деле это не так. Следующая функция process , которую мы рассмотрим, демонстрирует эти различия.

Мы раньше создали очень простой веб-сервер, который прослушивал запрос и выводил сообщение. Чтобы завершить программу, вам придется либо уничтожить процесс с использованием сигнала, либо нажать клавиши Ctrl+C. Также возможен другой вариант: завершить приложение из кода самого приложения при помощи process.exit() . Вы даже можете передать информацию о том, успешно ли завершилось приложение или произошла ошибка.

Мы изменим тестовое приложение ввода/вывода так, чтобы оно «прослушивало» строку выхода и при ее обнаружении программа завершалась.

process.stdin.setEncoding('utf8');

process.stdin.on('readable', function() {
  var input = process.stdin.read();
  if (input !== null) {
    // Эхо-вывод текста
    process.stdout.write(input);
    var command = input.trim();
    if (command == 'exit')
      process.exit(0);
  }
});

Если запустить это приложение, любая введенная строка будет немедленно повторяться в выводе. А если ввести команду exit , то приложение завершается, не требуя нажатия Ctrl+C.

Если убрать вызов функции process.stdin.setEncoding() в начале кода, произойдет ошибка. Дело в том, что буферы не поддерживают функцию trim(). Можно преобразовать буфер в строку, а затем выполнить trim:

var command = input.toString().trim();

Но лучше просто добавить команду назначения кодировки и исключить все неожиданные побочные эффекты.

Запись в объект process.stderr выполняется при возникновении ошибки. Почему именно этот объект, а не process.stdout ? По той же причине, по которой был создан канал stderr: чтобы отделить ожидаемый вывод от возникающих проблем. В некоторых системах вывод stderr даже может обрабатываться отдельно от stdout (например, сообщения stdout сохраняются в журнале, а вывод stderr выводится на консоль).

The post Глобальные объекты: Объекты global и process appeared first on HackerX.

]]>
https://hackerx.ru/node-global-objects/feed/ 0
Как создать собственные модули в Node.js и опубликовать в NPM https://hackerx.ru/node-js-npm-module/ https://hackerx.ru/node-js-npm-module/#respond Fri, 24 Nov 2017 12:37:45 +0000 https://hackerx.ru/?p=916 До того, как мы начнём разработать собственные модули в Node.js, давайте познакомимся с NPM. Узнаем что такое NPM, для чего он может понадобиться нам, как с ним можно работать, ознакомимся с основными командами и сделаем несколько важных настроек. Содержимое: Знакомство с NPM Создание и публикация собственных модулей Node Создание модуля Упаковка целого каталога Подготовка модуля к публикации Публикация модуля Знакомство с NPM […]

The post Как создать собственные модули в Node.js и опубликовать в NPM appeared first on HackerX.

]]>
До того, как мы начнём разработать собственные модули в Node.js, давайте познакомимся с NPM. Узнаем что такое NPM, для чего он может понадобиться нам, как с ним можно работать, ознакомимся с основными командами и сделаем несколько важных настроек.
Содержимое:

Знакомство с NPM

Значительная часть обширной функциональности, связанной с Node, реализована в модулях независимых разработчиков. Это модули маршрутизации, модули для работы с реляционными и документными СУБД, модули шаблонов, модули тестирования и даже модули платежных сервисов.

Чтобы использовать модуль, вы можете загрузить его исходный код, а затем установить его вручную в своей среде. Многие модули содержат базовые инструкции по установке, или, как минимум, требования к установке можно вычислить просмотром файлов и каталогов, включенных в модуль. Однако существует куда более простой способ установки модулей Node — менеджер npm.

Node.js поставляется с установленной копией npm, но эта версия npm не всегда является самой новой. Если вы захотите использовать другую версию npm, введите следующую команду для обновления имеющейся версии (используйте sudo , если этого требует ваша система):

npm install npm -g

Чтобы получить подробную сводку команд npm, воспользуйтесь следующей командой:

npm help npm

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

npm install имя_модуля

Например, для установки Express используется следующая команда:

npm install express

Программа npm не только устанавливает модуль Express, но и находит модули, от которых он зависит, и устанавливает их. Чем сложнее модуль, тем больше зависимостей устанавливается.

После того как модуль будет установлен, он будет находиться в каталоге node_modules вашего локального каталога. Все зависимости также будут установлены в каталог node_modules этого модуля.

Чтобы установить пакет глобально, используйте параметр -g или —global :

npm install express -g

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

Если пакет существует в нескольких версиях, есть возможность установить конкретную версию:

npm install module_name@0.3

Если модуль больше не используется, его можно удалить:

npm uninstall имя_модуля

Следующая команда приказывает npm проверить наличие новых модулей и обновить найденные:

npm update

Также можно обновить один модуль:

npm update имя_модуля

А если вы хотите узнать, есть ли среди пакетов устаревшие, воспользуйтесь командой:

npm outdated

Для вывода списка всех установленных пакетов и зависимостей передайте параметр list , ls , la или ll :

npm ls

С параметрами la и ll команды выводят расширенные описания. Например, в число зависимостей Request входит tunnel-agent@0.4.1 (версия 0.4.1 пакета tunnel-agent). Что это вообще такое — tunnel-agent? Ввод запроса npm la в командной строке выводит список всех зависимостей, включая tunnel-agent, с дополнительной информацией:

tunnel-agent@0.4.1
HTTP proxy tunneling agent. Formerly part of mikeal/request,
now a standalone module
git+https://github.com/mikeal/tunnel-agent.git
https://github.com/mikeal/tunnel-agent#readme

Флаг -d устанавливает все зависимости напрямую. Например, в каталоге модуля введите команду:

npm install -d

Чтобы узнать, какие модули установлены глобально, используйте следующую команду:

npm ls -g

Для получения дополнительной информации об установке npm используйте команду config. Следующая команда выводит список параметров конфигурации npm:

npm config list

Более подробное описание всех параметров конфигурации выводится командой:

npm config ls -l

Изменение или удаление параметров конфигурации из командной строки осуществляется либо командой:

npm config delete ключ
npm config set ключ значение

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

npm search html5 parser

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

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

И последнее замечание по поводу npm перед тем, как двигаться дальше. В самом начале ваших экспериментов с npm в конце вывода могут появляться предупреждения. В первой строке сообщается о том, что файл package.json не найден, а в остальных содержатся всевозможные предупреждения, связанные с отсутствующим файлом package.json .

Документация npm рекомендует создать файл package.json для сопровождения локальных зависимостей. В принципе, это не обязательно, но предупреждения слегка раздражают.

Чтобы создать файл package.json по умолчанию в каталоге проекта, выполните следующую команду:

npm init --yes

Команда создает в каталоге файл package.json по умолчанию; при этом вам будет предложено ответить на ряд вопросов по проекту: ваше имя, название проекта и т. д., причем у каждого вопроса имеется значение по умолчанию. В дальнейшем при установке модуля вы уже не будете получать часть раздражающих сообщений. Чтобы избавиться от остальных, необходимо обновить данные JSON в этом файле и включить в них описание и информацию о репозитории. Кроме того, если вы хотите, чтобы файл обновлялся при установке обновленного модуля, используйте следующий синтаксис:

npm install express --save-dev

Имя и версия модуля сохраняются в поле devDependencies файла package.json. Также можно сохранить модуль в зависимостях модулей, но об этом подробнее я расскажу в следующей части этой статьи.

Создание и публикация собственных модулей Node

Как и в случае с кодом JavaScript на стороне клиента, многократно используемый код JavaScript стоит выделить в собственные библиотеки. Единственное отличие заключается в том, что вам придется выполнить пару лишних действий для преобразования библиотеки JavaScript в модуль, рассчитанный на взаимодействие с Node.



Создание модуля

Допустим, у вас имеется библиотечная функция JavaScript concatArray, которая получает строку и массив строк, выполняет конкатенацию первой строки с каждой строкой в массиве и возвращает новый массив:

function concatArray(str, array) {
  return array.map(function(element) {
    return str + ' ' + element;
  });
}

Вы хотите использовать эту функцию наряду с другими в своих приложениях Node. Чтобы преобразовать библиотеку JavaScript для использования в Node, необходимо экспортировать все функции с помощью объекта exports , как показано в следующем примере:

exports.concatArray = function(str, array) {
  return array.map(function(element) {
    return str + ' ' + element;
  });
};

Чтобы использовать concatArray в приложении Node, импортируйте библиотеку. После этого вы сможете использовать экспортированную функцию в своем приложении:

const newArray = require('./arrayfunctions.js');
let data = newArray.concatArray('hello', ['test1','test2']);
console.log(data);

Вы также можете создать модуль, состоящий из конструктора или функции, и экспортировать его с использованием module.exports.

Например, модуль Mime , от которого могут зависеть многие другие модули, создает функцию Mime():

function Mime() { ... }

добавляет функциональность с использованием свойства prototype :

Mime.prototype.define = function(map) {...}

создает экземпляр по умолчанию:

const mime = new Mime();

присваивает функцию Mime одноименному свойству:

mime.Mime = Mime;

и после этого экспортирует экземпляр:

module.exports = mime;

После этого вы сможете использовать различные функции Mime в своем приложении:

const mime = require('mime');
console.log(mime.lookup('phoenix5a.png');

Упаковка целого каталога

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

Первый способ основан на создании файла package.json с информацией о каталоге. Структура содержит разнообразную информацию, но к упаковке модуля относятся два свойства — name и main:

{
  "name": "mylibrary",
  "main": "./mymodule/mylibrary.js"
}

Первое свойство, name , содержит имя модуля, а второе свойство, main, обозначает точку входа модуля.

Во втором способе загрузки содержимого в каталог добавляется файл index.js или index.node , служащий главной точкой входа модуля.

Зачем использовать каталог вместо одного модуля? Чаще всего это делается из-за того, что вы используете существующие библиотеки JavaScript и предоставляете файл — <<обертку>>, который <<упаковывает>> экспортируемые функции командой exports . А может быть, ваша библиотека настолько велика, что вы хотите разбить ее для удобства внесения изменений.

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

Подготовка модуля к публикации

Если вы захотите сделать свой пакет доступным для других, вы можете рекламировать его на своем сайте, но при этом вы упустите значительную аудиторию. Готовые модули лучше публиковать в реестре npm.

Ранее я упоминала о файле package.json. Документацию по JSON для npm можно найти по этому адресу https://docs.npmjs.com/files/package.json. Она базируется на рекомендациях системы модулей CommonJS.

В пакет package.json рекомендуется включать следующие поля:

  • name — имя пакета (обязательно).
  • description — описание пакета.
  • version — текущая версия, соответствующая требованиям семантической версии (обязательно).
  • keywords — массив условий поиска.
  • maintainers — массив ответственных за сопровождение пакета (с именами, адресами электронной почты и сайтами).
  • contributors — массив соавторов пакета (с именами, адресами электронной почты и сайтами).
  • bugs — URL-адрес для отправки ошибок.
  • licenses — массив лицензий.
  • repository — репозиторий пакетов.
  • dependencies — необходимые пакеты и их номера версий.

Обязательны только поля name и version , хотя включить рекомендуется все поля. К счастью, npm упрощает создание этого файла. Если ввести в командной строке следующую команду:

npm init

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

Сначала мы создадим в node_modules подкаталог с именем inputcheck и перенесем в него существующий код InputChecker. Файлу необходимо присвоить имя index.js . Затем в код вносятся изменения и из него извлекается часть, реализующая новый объект. Мы сохраним ее для будущего тестового файла.

const util = require('util');
const eventEmitter = require('events').EventEmitter;
const fs = require('fs');
exports.InputChecker = InputChecker;

function InputChecker(name, file) {
    this.name = name;
    this.writeStream = fs.createWriteStream(`./${file}.txt`,
        {'flags' : 'a',
            'encoding' : 'utf8',
            'mode' : 0666
        }
    );
};

util.inherits(InputChecker,eventEmitter);
InputChecker.prototype.check = (input) => {
    const command = input.toString().trim().substr(0,3);
    if (command == 'wr:') {
        this.emit('write', input.substr(3, input.length));
    } else if (command == 'en:') {
        this.emit('end');
    } else {
        this.emit('echo', input);
    }
};

Экспортировать функцию объекта напрямую не удастся, потому что util.inherits ожидает, что в файле с именем InputChecker существует объект. Прототип объекта InputChecker изменяется позднее в файле, также можно было изменить ссылки в коде и использовать exports.InputChecker , но это неуклюжее решение. С таким же успехом объект можно присвоить в отдельной команде.

Чтобы создать файл package.json, я выполнил команду npm init и ответил на все вопросы.

Сгенерированный файл package.json для модуля inputChecker:

{
  "name": "inputcheck",
  "version": "1.0.0",
  "description": "Looks for and implements commands from input",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "command",
    "check"
  ],
  "author": "HackerX.ru",
  "license": "ISC"
}

Команда npm init не запрашивает информацию о зависимостях, поэтому их придется добавить прямо в файл. Впрочем, есть другое, более правильное решение — использовать команду npm install —save при установке модулей, чтобы зависимости были добавлены автоматически. Однако модуль InputChecker не зависит от внешних модулей, поэтому эти поля можно оставить пустыми.

На этой стадии мы можем протестировать новый модуль и убедиться в том, что он действительно работает как модуль. В листинге 3.4 приведен фрагмент предыдущей версии приложения InputChecker, которая тестирует новый объект, теперь выделенный в отдельное тестовое приложение.

Тестовое приложение InputChecker:

const inputChecker = require('inputcheck').InputChecker;
const ic = new inputChecker('Shelley','output');

ic.on('write', (data) =>
  this.writeStream.write(data, 'utf8');
);

ic.addListener('echo', ( data) =>
    console.log(`${this.name} wrote ${data}`);
);

ic.on('end', () => process.exit());

process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', (input) => ic.check(input));

Теперь тестовое приложение можно переместить в новый подкаталог test в каталоге модуля, чтобы оно было упаковано в модуле как пример. По общепринятым правилам следует предоставить каталог test с одним или несколькими тестовыми приложениями, а также каталог doc с документацией. Для такого маленького модуля файла README должно быть достаточно.

Так как у нас теперь имеется тестовое приложение, необходимо внести изменения в файл package.json и добавить в него ссылку:

"scripts": {
  "test": "node test/test.js"
},

Это довольно примитивный тест, который не использует мощные тестовые возможности Node, но и он показывает, как может работать тестирование. Чтобы запустить тестовое приложение, введите в командной строке следующую команду:

npm test

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

Публикация модуля

Создатели npm также предоставили нам руководство с подробным описанием того, что необходимо сделать для публикации модуля Node: Developer Guide (https://docs.npmjs.com/misc/developers).

В документации приведены дополнительные требования к файлу package.json. В дополнение к уже созданным полям также необходимо добавить поле directories с хешем папок, включая уже упоминавшиеся test и doc:

"directories": {
  "doc" : ".",
  "test" : "test",
  "example" : "examples"
}

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

npm install . -g

К этому моменту мы протестировали модуль inputChecker, изменили файл package.json, добавили в него каталоги и убедились в том, что пакет успешно устанавливается.

Затем необходимо добавить себя как пользователей npm, если это не было сделано ранее. Введите следующую команду:

npm adduser

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

Осталось сделать последний шаг:

npm publish

Можно указать путь к tar-архиву или каталогу. Как предупреждает Guide, доступ предоставляется ко всему содержимому каталога, если только вы не используете в файле package.json директиву .npmignore со списком файлов для игнорирования материала. Тем не менее перед публикацией модуля лучше просто удалить все, без чего можно обойтись.

После публикации — и после того, как исходный код также будет загружен в GitHub (если вы используете этот репозиторий), — модуль официально готов для использования другими разработчиками. Рекламируйте свой модуль в Твиттере, Google+, Facebook, на своем сайте и вообще повсюду, где люди смогут о нем узнать. Такая реклама не хвастовство, а распространение информации.

The post Как создать собственные модули в Node.js и опубликовать в NPM appeared first on HackerX.

]]>
https://hackerx.ru/node-js-npm-module/feed/ 0
Node.js Promise — Использование промисов в серверной JavaScript https://hackerx.ru/node-js-promise/ https://hackerx.ru/node-js-promise/#respond Sat, 19 Aug 2017 08:01:14 +0000 https://hackerx.ru/?p=872 Обратные вызовы являются основой асинхронного программирования на JavaScript и в Node.js, но за прошедшие годы появились альтернативные подходы, позволяющие упростить работу с асинхронным кодом. В этой статье мы рассмотрим самые популярные из таких альтернатив, объекты Promise и генераторы, а также новейший синтаксис async await, который будет введен в JavaScript как часть спецификации ECMAScript 2017. Мы увидим, […]

The post Node.js Promise — Использование промисов в серверной JavaScript appeared first on HackerX.

]]>
Обратные вызовы являются основой асинхронного программирования на JavaScript и в Node.js, но за прошедшие годы появились альтернативные подходы, позволяющие упростить работу с асинхронным кодом.

В этой статье мы рассмотрим самые популярные из таких альтернатив, объекты Promise и генераторы, а также новейший синтаксис async await, который будет введен в JavaScript как часть спецификации ECMAScript 2017.

Мы увидим, как эти альтернативы могут упростить управление асинхронными потоками. И наконец, сравним все эти подходы, выявив плюсы и минусы каждого из них, чтобы иметь возможность разумно подойти к выбору подхода, наилучшим образом соответствующего требованиям проекта на платформе Node.js.

Объект Promise в JavaScript и Node.js

Стиль передачи продолжения (Continuation Passing Style, CPS) не является единственным способом реализации асинхронного кода. В действительности экосистема JavaScript предлагает интересные альтернативы традиционному шаблону обратных вызовов. Одной из самых распространенных таких альтернатив является объект Promise, которому уделяется все больше внимания, особенно сейчас, когда он стал частью спецификации ECMAScript 2015 и обрел встроенную поддержку, начиная с версии 4, платформы Node.js.

Что такое и для чего нужен Promise

Выражаясь простым языком, объект Promise является абстракцией, позволяющей функциям возвращать объект Promise, представляющий конечный результат асинхронной операции. Мы говорим, что объект Promise ожидает, если асинхронная операция еще не завершилась, выполнен – если операция завершилась успешно, и отклонен – если возникла ошибка. После того как объект Promise будет выполнен или отклонен, он считается установившимся.

Чтобы получить результат выполнения или ошибку (причину), вызвавшую отклонение, можно использовать метод then() объекта Promise:

promise.then([onFulfilled], [onRejected])

Здесь onFulflled() – это функция, которой передается результат выполнения асинхронной операции, а onRejected() – функция, которой передается причина отклонения. Обе функции являются необязательными.

Чтобы получить представление, как применение объектов Promise может изменить код, рассмотрим следующий фрагмент кода:

asyncOperation(arg, (err, result) => {
  if(err) {
    //обработка ошибки
  }
  //работа с результатом
});

Объекты Promise позволяют преобразовать этот типичный CPS-­код в более структурированный и элегантный код, например:

asyncOperation(arg)
  .then(result => {
    //работа с результатом
  }, err => {
    //обработка ошибки
  });

Одним из важнейших свойств метода then() является синхронный возврат другого объекта Promise. Если любая из функций – onFulflled() или onRejected() – вернет значение x, метод then() вернет один из следующих объектов Promise:

  • выполненный со значением x, если x является значением;
  • выполненный с объектом x, где x является объектом Promise или thenableобъектом;
  • отклоненный с причиной отклонения x, где x является объектом Promise или thenable­объектом.

 

thenable-объект – это Promise-подобный объект, имеющий метод then(). Этот термин используется для обозначения объекта, фактическая реализация которого отличается от реализации Promise.

Эта особенность позволяет создавать цепочки из объектов Promise, облегчая объединение и компоновку асинхронных операций в различных конфигурациях. Кроме того, если не указывается обработчик onFulflled() или onRejected(), результат или причина отклонения автоматически направляется следующему объекту Promise в цепочке. Это дает возможность, например, автоматически передавать ошибку вдоль всей цепочки, пока она не будет перехвачена обработчиком onRejected(). Составление цепочек объектов Promise делает последовательное выполнение заданий тривиальной операцией:

asyncOperation(arg)
  .then(result1 => {
    //возвращает другой объект Promise
    return asyncOperation(arg2);
  })
  .then(result2 => {
    //возвращает значение
    return 'done';
  })
  .then(undefined, err => {
    // здесь обрабатываются все возникшие в цепочке ошибки
  });

Схема на рисунке иллюстрирует другую точку зрения на работу цепочки объектов Promise:

объект Promise

Другим важным свойством объектов Promise является гарантированный асинхронный вызов функций onFulflled() и onRejected(), даже при синхронном выполнении, как в предыдущем примере, где последняя функция then() в цепочке возвращает строку ‘done’. Такая модель поведения защищает код от непреднамеренного высвобождения Залго, что без дополнительных усилий делает асинхронный код более последовательным и надежным.

А теперь самое интересное: если в обработчике onFulflled() или onRejected() возбудить исключение (оператором throw), возвращаемый методом then() объект Promise автоматически будет отклонен с исключением в качестве причины отказа. Это огромное преимущество перед CPS, потому что исключение автоматически будет передаваться вдоль по цепочке, а это означает, что можно использовать оператор throw.

Исторически сложилось, что существует множество библиотек, реализующих объекты Promise, большинство которых не совместимо друг с другом, что препятствует созданию then­цепочек из объектов Promise, созданных разными библиотеками.

Сообщество JavaScript провело сложную работу по преодолению этого ограничения, в результате была создана спецификация Promises / A+. Эта спецификация детально описывает поведение метода then и служит основой, обеспечивающей возможность взаимодействий между объектами Promise из различных библиотек.


Реализации Promises/A+

Как в JavaScript, так и в Node.js есть несколько библиотек, реализующих спецификацию Promises/A+. Ниже перечислены наиболее популярные из них:

  • Bluebird (https://npmjs.org/package/bluebird);
  • Q (https://npmjs.org/package/q);
  • RSVP (https://npmjs.org/package/rsvp);
  • Vow (https://npmjs.org/package/vow);
  • When.js (https://npmjs.org/package/when);
  • объекты Promise из ES2015.

 

По существу, они отличаются только наборами дополнительных возможностей, не предусмотренных стандартом Promises/A+. Как упоминалось выше, этот стандарт определяет модель поведения метода then() и процедуру разрешения объекта Promise, но не регламентирует других функций, например порядка создания объекта Promise на основе асинхронной функции с обратным вызовом.

В примерах ниже мы будем использовать методы, поддерживаемые объектами Promise стандарта ES2015, поскольку они доступны в Node.js, начиная с версии 4, и не требуют подключения внешних библиотек.

Для справки ниже перечислены методы объектов Promise, определяемые стандартом ES2015.

Конструктор (new Promise(function(resolve, reject) {})): создает новый объект Promise, который разрешается или отклоняется в зависимости от функции, переданной в аргументе. Конструктору можно передать следующие аргументы:

  • resolve(obj): позволяет разрешить объект Promise и вернуть результат obj, если obj является значением. Если obj является другим объектом Promise или thenable­объектом, результатом станет результат выполнения obj;
  • reject(err): отклоняет объект Promise с указанной причиной err. В соответствии с соглашением err должен быть экземпляр Error.

 

Статические методы объекта Promise:

  • Promise.resolve(obj): возвращает новый объект Promise, созданный из thenableобъекта, если obj – thenable­объект, или значение, если obj – значение;
  • Promise.all(iterable): создает объект Promise, который разрешается результатами выполнения, если все элементы итерируемого объекта iterable выполнились, и отклоняется при первом же отклонении любого из элементов. Любой элемент итерируемого объекта может быть объектом Promise, универсальным thenable­объектом или значением;
  • Promise.race(iterable): возвращает объект Promise, разрешаемый или отклоняемый, как только разрешится или будет отклонен хотя бы один из объектов Promise в итерируемом объекте iterable, со значением или причиной этого объекта Promise.

 

Методы экземпляра Promise:

  • promise.then(onFulflled, onRejected): основной метод объекта Promise. Его модель поведения совместима со стандартом Promises/A+, упомянутым выше;
  • promise.catch(onRejected): удобная синтаксическая конструкция, заменяющая promise.then(undefned, onRejected).

 

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

Перевод функций в стиле Node.js на использование объектов Promise

В JavaScript не все асинхронные функции и библиотеки поддерживают объекты Promise изначально. Обычно типичные функции, основанные на обратных вызовах, требуется преобразовать так, чтобы они возвращали объекты Promise. Этот процесс называется переводом на использование объектов Promise.

К счастью, соглашения об обратных вызовах, используемые на платформе Node.js, позволяют создавать функции, способные переводить любые функции в стиле Node.js на использование объектов Promise. Это несложно осуществить с помощью конструктора объекта Promise. Создадим новую функцию promisify() и добавим ее в модуль utilities.js (чтобы ее можно было использовать в приложении веб­паука):

module.exports.promisify = function(callbackBasedApi) {
  return function promisified() {
    const args = [].slice.call(arguments);
    return new Promise((resolve, reject) => {  //[1]
      args.push((err, result) => {             //[2]
        if(err) {
          return reject(err);                  //[3]
        }
        if(arguments.length <= 2) {            //[4]
          resolve(result);
        } else {
          resolve([].slice.call(arguments, 1));
        }
     });
     callbackBasedApi.apply(null, args);      //[5]
   });
 }
};

Приведенная выше функция возвращает другую функцию – promisifed(), которая является версией callbackBasedApi, возвращающей объект Promise. Вот как она работает:

  1. функция promisifed() создает новый объект с помощью конструктора Promise и немедленно возвращает его;
  2. в функции, что передается конструктору Promise, мы передаем специальную функцию обратного вызова для вызова из callbackBasedApi. Поскольку функция обратного вызова всегда передается в последнем аргументе, мы просто добавляем ее в список аргументов (args) функции promisifed();
  3. если специальная функция обратного вызова получит ошибку, объект Promise немедленно отклоняется;
  4. в случае отсутствия ошибки осуществляется разрешение объекта Promise со значением или массивом значений, в зависимости от количества результатов, переданных функции обратного вызова;
  5. в заключение вызывается callbackBasedApi с созданным списком аргументов.

 

Большинство реализаций поддерживает вспомогательный метод преобразования типичных функций в стиле Node.js в функции, возвращающие объекты Promise. Например, библиотека Q содержит функции Q.denodeify() и Q.nbind(), библиотека Bluebird имеет Promise.promisify(), а When.js содержит node.lift().

Последовательное выполнение Промисов

Теперь, после знакомства с теорией, можно приступать к созданию приложений. Рассмотрим работу на примере создание веб-паука:

Код, который представлен ниже, не будет работать. Это просто пример использование.

const utilities = require('./utilities');
const request = utilities.promisify(require('request'));
const mkdirp = utilities.promisify(require('mkdirp'));
const fs = require('fs');
const readFile = utilities.promisify(fs.readFile);
const writeFile = utilities.promisify(fs.writeFile);

function download(url, filename) {
  console.log(`Downloading ${url}`);
  let body;
  return request(url)
    .then(response => {
      body = response.body;
      return mkdirp(path.dirname(filename));
    })
    .then(() => writeFile(filename, body))
    .then(() => {
      console.log(`Downloaded and saved: ${url}`);
      return body;
    });
}

Promise: Последовательные итерации

На данный момент, на примере приложения веб­-паука, мы рассмотрели объекты Promise и приемы их использования для создания простой элегантной реализации последовательного потока выполнения. Но этот код обеспечивает выполнение лишь известного заранее набора асинхронных операций. Поэтому, чтобы восполнить пробелы в исследовании последовательного выполнения, нам нужно разработать фрагмент, реализующий итерации с помощью объектов Promise. И снова прекрасным примером для демонстрации станет функция spiderLinks().

function spiderLinks(currentUrl, body, nesting) {
  let promise = Promise.resolve();
  if(nesting === 0) {
    return promise;
  }
  const links = utilities.getPageLinks(currentUrl, body);
  links.forEach(link => {
    promise = promise.then(() => spider(link, nesting – 1));
  });
  return promise;
}

Для асинхронного обхода всех ссылок на веб-­странице нужно динамически создать цепочку объектов Promise.

  1. Начнем с определения «пустого» объекта Promise, разрешаемого как undefned. Он будет служить началом цепочки.
  2. Затем в цикле присвоим переменной promise новый объект Promise, полученный вызовом метода then() предыдущего объекта Promise в цепочке. Это и есть шаблон асинхронных итераций с использованием объектов Promise.

 

В конце цикла переменная promise будет содержать объект Promise, который вернул последний вызов then() в цикле, поэтому он будет разрешен после разрешения всех объектов Promise в цепочке.

The post Node.js Promise — Использование промисов в серверной JavaScript appeared first on HackerX.

]]>
https://hackerx.ru/node-js-promise/feed/ 0
Node.js: Паттерны / шаблоны проектирования https://hackerx.ru/node-js-shabloni-proektirovanie/ https://hackerx.ru/node-js-shabloni-proektirovanie/#respond Fri, 18 Aug 2017 13:43:46 +0000 https://hackerx.ru/?p=868 Освоение асинхронной природы платформы Node.js, вообще говоря, не тривиальная задача, особенно для тех, кто переходит на нее с таких языков, как PHP, где обычно не приходится иметь дело с асинхронным кодом. В синхронном программировании используется привычная концепция, представляющая код как ряд последовательных вычислений, направленных на решение конкретной задачи. Любая операция является блокирующей, поскольку только после […]

The post Node.js: Паттерны / шаблоны проектирования appeared first on HackerX.

]]>
Освоение асинхронной природы платформы Node.js, вообще говоря, не тривиальная задача, особенно для тех, кто переходит на нее с таких языков, как PHP, где обычно не приходится иметь дело с асинхронным кодом.

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

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

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



Платформа Node.js предлагает ряд инструментов и шаблонов проектирования для разработки оптимального асинхронного кода. Важно освоить их, чтобы уверенно ими пользоваться для создания производительных приложений, простых для понимания и отладки.

В следующей статье начнём знакомиться с двумя важных шаблонов асинхронной обработки: Callback (Обратный вызов) и Event Emitter (Генератор событий).

The post Node.js: Паттерны / шаблоны проектирования appeared first on HackerX.

]]>
https://hackerx.ru/node-js-shabloni-proektirovanie/feed/ 0
Node.js Child Process — Создание и работа с дочерними процессами в Node https://hackerx.ru/node-js-child-process/ https://hackerx.ru/node-js-child-process/#respond Fri, 18 Aug 2017 12:10:11 +0000 https://hackerx.ru/?p=833 Child Process — это модуль в Node.js, который можно подключить с помощью функции require(). Используя модуль child_process мы можем полноценно использовать функционал процессов. Ниже перечислены некоторые возможности модуля: С помощью модуля child_process мы можем запускать команды shell. Shell — это командный язык высокого уровня, который используется в Unix как интерпретатор. Он совместим с Bash. С помощью простых Shell команд мы можем управлять сервером и написать […]

The post Node.js Child Process — Создание и работа с дочерними процессами в Node appeared first on HackerX.

]]>
Child Process — это модуль в Node.js, который можно подключить с помощью функции require(). Используя модуль child_process мы можем полноценно использовать функционал процессов. Ниже перечислены некоторые возможности модуля:

  • С помощью модуля child_process мы можем запускать команды shell. Shell — это командный язык высокого уровня, который используется в Unix как интерпретатор. Он совместим с Bash. С помощью простых Shell команд мы можем управлять сервером и написать сложные сценарий.
  • Модуль child_process позволяет запускать параллельные дочерние процессы.
  • Модуль child_process позволяет обмениваться сообщениями между собой.

 

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

Метод child_process.exec()

С помощью метода child_process.exec() мы можем запускать команды на выполнения. После выполнения результат будет сохранен в буфере.

Пример использование метода child_process.exec():

const exec = require("child_process").exec;

const child = exec('cat *.js not_exist_file | wc -1', {cwd: '/'}, (err, stdout, stderr) => {
  console.log('stdout: ' + stdout);
  console.log('stderr: ' + stderr);
  if(err){
    console.log('exec error: ' + err);
  }
});

Тут мы с помощью метода child_process.exec() запускаем shell-команду cat, выводящую содержимое нескольких файлов и через пайп передающую их команде wc, считывающей количество строк. Вторым аргументом передается массив параметров в данном случае мы изменяем рабочую директорию. Метод в качестве третьего аргумента принимает функцию, который принимает 3 параметра. Первый параметр — это объект ошибки, а второй и третий — это потоки stdout и stderr. Ценность команды состоит в возможности действий с полученными результатами.

Метод child_process.spawn()

Следующий метод — это child_process.spawn(), который запускает команду в новом процессе, дескриптор которого становится доступным:

const spawn = require("child_process").spawn;
let ls = spawn('ls', ['-lh', '/user']);

ls.stdout.on('data', (data) => {
  console.log("stdout: " + data);
});

ls.stderr.on('data', (data) => {
  console.log("stderr: " + data);
});

ls.on('close', (code) => {
  console.log("child process exited with code: " + data);
});

Здесь все просто — любой вывод исполняемой команды передается в поток stdout дочернего процесса (или stderr, если случилось ошибка), и мы связали обработчики именно с этими объектами. Мы видим, что поток выполняется параллельно с основным процессом и завершается с кодом 0, что свидетельствует об успехе.


Метод child_process.fork()

Следующий метод — это child_process.fork(), который запускает дочерним процессом новый процесс, порожденный самой Node.js. Ниже представлен простой пример работы данного метода:

const sp = require("child_process");

let child1 = cp.fork(`${__dirname}/sub1.js`);
let child2 = cp.fork(`${__dirname}/sub2.js`);

while(cp){
  console.log("running main");
}

Тут мы запускаем два дочерних процесса, исполняющих код, характеризующихся в файлах sub1.js и sub2.js, и создаем бесконечный цикл вывода сообщений. Ниже представлен содержимое дочерних процессов:

sub1.js:

while(1){
  console.log("running: process 1");
}

sub2.js:

while(1){
  console.log("running: process 2");
}

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

Метод child_process.send()

Последний метод, который будет рассмотрен в этом статье — это метод send() модуля Child Process. Он отсылает сообщения от одного процесса к другому:

main.js:

const sp = require("child_process");

let child = cp.fork(`${__dirname}/sub.js`);

child.on("message", (data) => {
  console.log("Main got message: " + data);
});

child.send({'hello': 'child'});

sub.js:

process.on("message", (m) => {
  console.log("Child got message: " + m);
});

process.send({'foo': 'bar'});

В виде сообщение также можно посылать команды управления.

The post Node.js Child Process — Создание и работа с дочерними процессами в Node appeared first on HackerX.

]]>
https://hackerx.ru/node-js-child-process/feed/ 0
Node.js — Процессы. Свойства и методы объекта Process https://hackerx.ru/node-js-process/ https://hackerx.ru/node-js-process/#respond Mon, 14 Aug 2017 14:18:15 +0000 https://hackerx.ru/?p=792 Любое приложение созданное на Node.js — это экземпляр объекта Process, который наследует все свойства и методы этого объекта. С помощью этих свойств и методов мы можем получить информацию о приложении и контексте его исполнения. Ниже представлен пример работы некоторых свойств, назначение которых очевидно из названий. Для использование данного примера, нужно создать .js файл, вводить код примера и запустить файл с терминала (командная строка). […]

The post Node.js — Процессы. Свойства и методы объекта Process appeared first on HackerX.

]]>
Любое приложение созданное на Node.js — это экземпляр объекта Process, который наследует все свойства и методы этого объекта. С помощью этих свойств и методов мы можем получить информацию о приложении и контексте его исполнения. Ниже представлен пример работы некоторых свойств, назначение которых очевидно из названий.

Для использование данного примера, нужно создать .js файл, вводить код примера и запустить файл с терминала (командная строка).

console.log(process.execPath);
console.log(process.version);
console.log(process.platform);
console.log(process.arch);
console.log(process.title);
console.log(process.pid);

Если используете операционную систему Windows, то результат может быть примерно такой:

C:\Program Files\nodejs\node.exe
v8.1.4
win32
x64
cmd — node test.js
3712

Еще две важные и часто используемые свойства объекта Process — это process.moduleLoadList и process.argv. С помощью свойство moduleLoadList мы можем получить информацию о загруженных модулях, а с помощью argv мы можем получить весь список команд из командной строки (терминал / cmd).

process.argv возвращает массив аргументов командной строки. Давайте рассмотрим это на примере. Нам нужно создать .js файл с таким содержанием:

console.log( process.argv );

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



Без дополнительных параметров:

node test.js

Результат:

[ ‘C:\\Program Files\\nodejs\\node.exe’,
‘C:\\Users\\Voskan\\Desktop\\primeri\\test.js’ ]

Результатом будет массив параметров командной строки. Всегда первый элемент массива — это имя / адрес исполняемого правления, то есть адрес интерпретатора Node.js, второй параметр — это файл который исполняется.

А сейчас посмотрим пример с доп. параметрами:

node test.js foo bar kuku hello

Результат:

[ ‘C:\\Program Files\\nodejs\\node.exe’,
‘C:\\Users\\Voskan\\Desktop\\primeri\\test.js’,
‘foo’,
‘bar’,
‘kuku’,
‘hello’ ]

Мы видим, что свойство process.argv возвращает все команды / параметры терминала / консоли в виде массива данных.

Следующая команда / метод — это process.exit(), с помощью которого можно завершить / закрыть процесс. Метод в качестве параметра принимает число / статус код. По умолчанию это 0, который означает, что процесс завершен успешно. Чтобы завершить / выйти с ошибкой, нужно передать 1 — process.exit(1).

Код выхода определяется с помощью событие exit:

process.on("exit", (code) => {
  console.log("Процесс завершен с кодом: " + code);
});

Еще один полезный метод — это process.memoryUsage(), который возвращает информацию о потребление памяти процессом Node в байтах. Метод возвращает информацию в виде объекта.

Пример использование метода process.memoryUsage():

console.log( process.memoryUsage() );

let arr = [];

for(let i = 0; i <= 50000; i++){
    arr[i] = "String " + i;
}

console.log( process.memoryUsage() );

Результат:

{ rss: 21565440,
heapTotal: 6291456,
heapUsed: 4411624,
external: 8252 }

{ rss: 27615232,
heapTotal: 16777216,
heapUsed: 8688976,
external: 8252 }

heapTotal и heapUsed характеризуют потребление памяти JavaScript движка V8.

The post Node.js — Процессы. Свойства и методы объекта Process appeared first on HackerX.

]]>
https://hackerx.ru/node-js-process/feed/ 0