The post Сжатие / восстановление данных с использованием библиотеки Zlib appeared first on HackerX.
]]>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.
]]>The post Node.js: Потоки (Stream) и pipe() appeared first on HackerX.
]]>Поток представлен абстрактным интерфейсом, это означает, что вы не будете создавать потоки напрямую. Вместо этого вы будете работать с различными объектами, реализующими интерфейс Stream, — запросами HTTP, потоками для чтения или записи модуля File System, объектами сжатия Zlib или process.stdout. Непосредственно реализовать Stream API вам придется только в одном случае: при создании собственной реализации потока.
Так как многие объекты в Node реализуют потоковый интерфейс, все потоки в Node обладают базовой функциональностью:
Обратите внимание на пункт с проверкой чтения и (или) записи. Поток с поддержкой чтения и записи называется дуплексным. Также существует подвид дуплексных потоков, называемый потоком преобразования данных, в котором ввод и вывод связаны причинной зависимостью. Такая разновидность потоков будет описана позднее, когда я буду рассматривать сжатие 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.
]]>The post Node.js — Модуль OS: Работа с операционной системой appeared first on HackerX.
]]>Для непосредственного получения информации об операционной системе используется базовый модуль 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.
]]>The post Webpack и Node.js: Руководство по использованию appeared first on HackerX.
]]>Разрабатывая приложения для платформы Node.js, старайтесь не использовать систем модулей, отличных от той, что предлагается по умолчанию. В идеале нужно продолжать писать модули как обычно, используя require() и module.exports, а затем применять инструмент для преобразования кода в пакет, который будет нормально работать в браузере. К счастью, эта задача имеет множество решений, самым попуярным из которых является Webpack (https://webpack.github.io).
Webpack позволяет создавать модули с использованием соглашений о модулях Node.js, с последующей компиляцией в пакет (единственный JavaScript-файл), содержащий все необходимое для работы в браузере (в том числе абстрактные функции require()). Такой пакет затем можно включить в веб-страницу, как обычно, и выполнить в браузере. Webpack рекурсивно сканирует исходный код, отыскивает ссылки на функцию require(), разрешает их и добавляет нужные модули в пакет.
Для быстрой демонстрации волшебства 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 настолько привлекательны, что многие разработчики начали использовать его даже для управления кода, предназначенного только для выполнения на стороне клиента. Это стало возможным потому, что многие клиентские библиотеки начали по умолчанию поддерживать CommonJS и npm, что открывает новые и очень интересные перспективы. Например, можно установить библиотеку jQuery:
npm install jquery
а затем загрузить ее с помощью простой строки кода:
const $ = require('jquery');
Вы будете удивлены, как много клиентских библиотек уже поддерживают CommonJS и Webpack.
The post Webpack и Node.js: Руководство по использованию appeared first on HackerX.
]]>The post Глобальные объекты: Объекты global и process appeared first on HackerX.
]]>В браузере переменная, объявленная на верхнем уровне, объявляется глобально. В 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 принадлежит к числу важнейших компонентов среды 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:
Вы не можете закрывать эти потоки в своем приложении. Поддерживается только чтение данных из канала 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.
]]>The post ICO — Telegram Open Network (TON). Криптовалюта / Token Gram appeared first on HackerX.
]]>Зачем Телеграмму ICO? Если вы пользуетесь этим мессенджером то наверняка заметили отсутствие рекламы, а возможно слышали громкие заявления Павла Дурова о том, что никакой рекламы не будет. Это может означать только одно, что проект в данный момент не приносит доход, но при этом требует существенных вложений от своего создателя. Один из самых перспективных способов выйти из сложившийся ситуации, это интегрировать в Telegram криптовалюту, как это сделал WeChat с NEM.
Криптовалюта / токен Telegram получит название GRAM, а сама блокчейн-платформа будет именоваться TON (Telegram Open Network). К 2021 году после создания полноценной блокчейн-экосистемы и внутренней экономики Telegram Open Network сменит название на The Open Network.
Такого масштабного проекта еще не было в истории ICO, это конечно большой шаг в развитии криптовалюты и не удивительно, что многие пророчат большую прибыль инвесторам и оснований для этого предостаточно:
Дата выхода анонса TON и GRAM: ориентировочно в марте 2018 года;
Полный запуск экосистемы Blockchain TON: Q2 2019;
Общая эмиссия монет GRAM: 5 млрд. токенов;
Pre-ICO TON: Q1 2018, минимальная сумма от $20 млн. (стоимость монеты от $0,38)
Pre-ICO-2 (второй этап) TON: Q1 2018, минимальная сумма от $1 млн. для физлиц и от $10 для юр.лиц. (стоимость монеты от $1,33).
Самые очевидные способы применения могут быть такими:
Попасть на Pre-ICO Telegram простым смертным практически не реально, если конечно у вас не завалялись миллионов 20 долларов, так что придется ждать публичной продажи или объединяться в пул, чтобы собрать минимальную необходимую сумму. Если вы решитесь на такой шаг, то будьте крайне осторожны, скорее всего человек предложивший передать ваши деньги Павлу будет мошенником! Во время открытого ICO мессенджер намерен продать токенов на сумму от 3 до 5 миллиардов долларов, но в продажу поступят не все токены, а только 44%, часть оставят разработчикам 4%, а львиную долю 52% сохранят в качестве резерва.
The post ICO — Telegram Open Network (TON). Криптовалюта / Token Gram appeared first on HackerX.
]]>The post Machine Learning: Зачем нужно машинное обучение appeared first on HackerX.
]]>Один из примеров, где этот жесткий подход потерпит неудачу – это распознавание лиц на изображениях. На сегодняшний день каждый смартфон может распознать лицо на изображении. Тем не менее, распознавание лиц была нерешенной проблемой, по крайней мере, до 2001 года. Основная проблема заключается в том, что способ, с помощью которого компьютер «воспринимает» пиксели, формирующие изображение на компьютере, очень сильно отличается от человеческого восприятия лица. Эта разница в принципе не позволяет человеку сформулировать подходящий набор правил, описывающих лицо с точки зрения цифрового изображения.
Однако, благодаря машинному обучению, простого предъявления большого количества изображений с лицами будет достаточно для того, чтобы алгоритм определил, какие признаки необходимы для идентификации лица.
Наиболее успешные алгоритмы машинного обучения – это те, которые автоматизируют процессы принятия решений путем обобщения известных примеров. В этих методах, известных как обучение с учителем или контролируемое обучение ( supervised learning ), пользователь предоставляет алгоритму пары объект-ответ, а алгоритм находит способ получения ответа по объекту. В частности, алгоритм способен выдать ответ для объекта, которого он никогда не видел раньше, без какой-либо помощи человека. Если вернуться к примеру классификации спама с использованием машинного обучения, пользователь предъявляет алгоритму большое количество писем (объекты) вместе с информацией о том, является ли письмо спамом или нет (ответы). Для нового электронного письма алгоритм вычислит вероятность, с которой это письмо можно отнести к спаму.
Алгоритмы машинного обучения, которые учатся на парах объект-ответ, называются алгоритмами обучения с учителем, так как «учитель» показывает алгоритму ответ в каждом наблюдении, по которому происходит обучение. Несмотря на то, что создание набора с объектами и ответами – это часто трудоемкий процесс, осуществляемый вручную, алгоритмы обучения с учителем интерпретируемы и качество их работы легко измерить. Если вашу задачу можно сформулировать в виде задачи обучения с учителем, и вы можете создать набор данных, который включает в себя ответы, вероятно, машинное обучение решит вашу проблему.
Примеры задач машинного обучения с учителем:
Определение почтового индекса по рукописным цифрам на конверте.
Здесь объектом будет сканированное изображение почерка, а ответ – фактические цифры почтового индекса. Чтобы создать набор данных для построения модели машинного обучения, вам нужно собрать большое количество конвертов. Затем вы можете самостоятельно прочитать почтовые индексы и сохранить цифры в виде ответов.
Определение доброкачественности опухоли на основе медицинских изображений.
Здесь объектом будет изображение, а ответом – диагноз, является ли опухоль доброкачественной или нет. Чтобы создать набор данных для построения модели, вам нужна база медицинских изображений. Кроме того, необходимо мнение эксперта, поэтому врач должен просмотреть все изображения и решить, какие опухоли являются доброкачественными, а какие – нет. Помимо анализа изображения может понадобиться дополнительная диагностика для определения доброкачественности опухоли.
Обнаружение мошеннической деятельности в сделках по кредитным картам.
Здесь объект – запись о транзакции по кредитной карте, а ответ — информация о том, является ли транзакция мошеннической или нет. Предположим, вы – учреждение, выдающее кредитные карты, сбор данных подразумевает сохранение всех транзакций и запись сообщений клиентов о мошеннических транзакциях.
Приведя эти примеры, интересно отметить что, хотя объекты и ответы выглядят достаточно просто, процесс сбора данных для этих трех задач существенно отличается. Несмотря на то что чтение конвертов является трудоемким занятием, этот процесс прост и дешев. Получение медицинских изображений и проведение диагностики требует не только дорогостоящего оборудования, но и редких, высокооплачиваемых экспертных знаний, не говоря уже об этических проблемах и вопросах конфиденциальности. В примере обнаружения мошенничества с кредитными картами, сбор данных осуществляется намного проще. Ваши клиенты сами предоставят вам ответы, сообщая о мошенничестве. Все, что вам нужно сделать для получения объектов и ответов, связанных с мошеннической активностью, – это подождать.
Алгоритмы обучения без учителя или неконтролируемого обучения (unsupervised algorithms) – это еще один вид алгоритмов, который мы рассмотрим в этой книге. В алгоритмах обучения без учителя известны только объекты, а ответов нет. Хотя есть много успешных сфер применения этих методов, их, как правило, труднее интерпретировать и оценить.
Примеры задач машинного обучения без учителя:
Определение тем в наборе постов.
Если у вас есть большая коллекция текстовых данных, вы можете агрегировать их и найти распространенные темы. У вас нет предварительной информации о том, какие темы там затрагиваются и сколько их. Таким образом, нет никаких известных ответов.
Сегментирование клиентов на группы с похожими предпочтениями.
Имея набор записей о клиентах, вы можете определить группы клиентов со схожими предпочтениями. Для торгового сайта такими группами могут быть «родители», «книгочеи» или «геймеры». Поскольку вы не знаете заранее о существовании этих групп и их количестве, у вас нет ответов.
Обнаружение паттернов аномального поведения на веб-сайте.
Чтобы выявить злоупотребления или ошибки, часто бывает полезно найти паттерны поведения, которые отличаются от нормы. Паттерны аномального поведения могут быть разными, и, возможно, у вас не будет зарегистрированных случаев аномального поведения. Поскольку в этом примере вы наблюдаете лишь трафик, и вы не знаете, что представляет собой нормальное и ненормальное поведение, речь идет о задаче обучения без учителя.
The post Machine Learning: Зачем нужно машинное обучение appeared first on HackerX.
]]>The post Как создать собственные модули в Node.js и опубликовать в NPM appeared first on HackerX.
]]>Значительная часть обширной функциональности, связанной с 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. Также можно сохранить модуль в зависимостях модулей, но об этом подробнее я расскажу в следующей части этой статьи.
Как и в случае с кодом 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 и 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.
]]>The post Node.js Promise — Использование промисов в серверной JavaScript appeared first on HackerX.
]]>В этой статье мы рассмотрим самые популярные из таких альтернатив, объекты Promise и генераторы, а также новейший синтаксис async await, который будет введен в JavaScript как часть спецификации ECMAScript 2017.
Мы увидим, как эти альтернативы могут упростить управление асинхронными потоками. И наконец, сравним все эти подходы, выявив плюсы и минусы каждого из них, чтобы иметь возможность разумно подойти к выбору подхода, наилучшим образом соответствующего требованиям проекта на платформе Node.js.
Стиль передачи продолжения (Continuation Passing Style, CPS) не является единственным способом реализации асинхронного кода. В действительности экосистема JavaScript предлагает интересные альтернативы традиционному шаблону обратных вызовов. Одной из самых распространенных таких альтернатив является объект Promise, которому уделяется все больше внимания, особенно сейчас, когда он стал частью спецификации ECMAScript 2015 и обрел встроенную поддержку, начиная с версии 4, платформы Node.js.
Выражаясь простым языком, объект 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:
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 является гарантированный асинхронный вызов функций onFulflled() и onRejected(), даже при синхронном выполнении, как в предыдущем примере, где последняя функция then() в цепочке возвращает строку ‘done’. Такая модель поведения защищает код от непреднамеренного высвобождения Залго, что без дополнительных усилий делает асинхронный код более последовательным и надежным.
А теперь самое интересное: если в обработчике onFulflled() или onRejected() возбудить исключение (оператором throw), возвращаемый методом then() объект Promise автоматически будет отклонен с исключением в качестве причины отказа. Это огромное преимущество перед CPS, потому что исключение автоматически будет передаваться вдоль по цепочке, а это означает, что можно использовать оператор throw.
Исторически сложилось, что существует множество библиотек, реализующих объекты Promise, большинство которых не совместимо друг с другом, что препятствует созданию thenцепочек из объектов Promise, созданных разными библиотеками.
Сообщество JavaScript провело сложную работу по преодолению этого ограничения, в результате была создана спецификация Promises / A+. Эта спецификация детально описывает поведение метода then и служит основой, обеспечивающей возможность взаимодействий между объектами Promise из различных библиотек.
Как в JavaScript, так и в Node.js есть несколько библиотек, реализующих спецификацию Promises/A+. Ниже перечислены наиболее популярные из них:
По существу, они отличаются только наборами дополнительных возможностей, не предусмотренных стандартом Promises/A+. Как упоминалось выше, этот стандарт определяет модель поведения метода then() и процедуру разрешения объекта Promise, но не регламентирует других функций, например порядка создания объекта Promise на основе асинхронной функции с обратным вызовом.
В примерах ниже мы будем использовать методы, поддерживаемые объектами Promise стандарта ES2015, поскольку они доступны в Node.js, начиная с версии 4, и не требуют подключения внешних библиотек.
Для справки ниже перечислены методы объектов Promise, определяемые стандартом ES2015.
Конструктор (new Promise(function(resolve, reject) {})): создает новый объект Promise, который разрешается или отклоняется в зависимости от функции, переданной в аргументе. Конструктору можно передать следующие аргументы:
Статические методы объекта Promise:
Методы экземпляра Promise:
Стоит отметить, что некоторые реализации предлагают другой асинхронный механизм – механизм отложенных вычислений. Мы не будем рассматривать его, поскольку он не является частью стандарта ES2015.
В 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. Вот как она работает:
Большинство реализаций поддерживает вспомогательный метод преобразования типичных функций в стиле 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. И снова прекрасным примером для демонстрации станет функция 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.
В конце цикла переменная promise будет содержать объект Promise, который вернул последний вызов then() в цикле, поэтому он будет разрешен после разрешения всех объектов Promise в цепочке.
The post Node.js Promise — Использование промисов в серверной JavaScript appeared first on HackerX.
]]>The post Node.js: Паттерны / шаблоны проектирования appeared first on HackerX.
]]>В синхронном программировании используется привычная концепция, представляющая код как ряд последовательных вычислений, направленных на решение конкретной задачи. Любая операция является блокирующей, поскольку только после ее завершения можно переходить к выполнению следующей. Такой подход упрощает понимание и отладку кода.
Напротив, в асинхронном программировании некоторые операции, например чтение файла или сетевой запрос, могут выполняться в фоновом режиме. После вызова асинхронной операции следующая операция выполняется немедленно, даже если предыдущая операция еще не закончила выполняться. Операции в фоновом режиме могут закончить свое выполнение в любое время, и все приложение должно быть реализовано так, чтобы должным образом отреагировать на завершение асинхронного вызова.
Хотя этот неблокирующий подход практически всегда гарантирует более высокую производительность, по сравнению с всегда блокирующим сценарием, он привносит трудную для восприятия парадигму, которая может стать весьма громоздкой в больших приложениях, требующих сложного управления потоками.
Платформа Node.js предлагает ряд инструментов и шаблонов проектирования для разработки оптимального асинхронного кода. Важно освоить их, чтобы уверенно ими пользоваться для создания производительных приложений, простых для понимания и отладки.
В следующей статье начнём знакомиться с двумя важных шаблонов асинхронной обработки: Callback (Обратный вызов) и Event Emitter (Генератор событий).
The post Node.js: Паттерны / шаблоны проектирования appeared first on HackerX.
]]>