Оригинал: New io.js Features You May Not Be Using
Платформа io.js развивается быстрыми темпами, оказывая большое влияние на всю экосистему Node. За короткое время в io.js было внесено множество исправлений, улучшена производительность, а также добавлены новые возможности и функции.
Если Вы не следили за развитием io.js, ничего страшного — в данной статье мы вкратце представим основные возможности платформы. Рассматривать их мы будет по мере выхода, то есть от старых к новым. Где это будет нужным, дадим ссылки на соответствующую документацию (на английском языке). Описывая каждую новую функциональность, будем указывать ее первую версию. Код написан с использованием стандартных функций ES6, которые доступны в io.js. Итак, начнем…
Первоначально одной из задач разработчиков io.js было приблизиться к функциональности V8, а именно: полноценно использовать все имеющиеся возможности ES6. Считалось, что встроенные по умолчанию генераторы, так же, как и другие функции ES6, будут работать достаточно стабильно и без флагов. Советуем не спешить с поиском информации по ES6 — для начала ознакомьтесь с документацией, ссылки на которую мы дадим в статье.
Ссылка на документацию: Функции ES6
Отметим: По умолчанию в Node 0.12 присутствуют некоторые функции ES6. Под флагом --harmony можно найти еще больше функций. Однако, работа и доступность функций ES6 отличается от io.js (например: в Node генераторы по умолчанию не присутствуют).
Когда-нибудь задавались вопросом, что такое EPIPE, EMFILE, ENOENT? Я лично задавал себе этот вопрос и не раз. Слава богу, теперь в io.js появилась значительно улучшенная документация по ошибкам.
Ссылка на документацию: Документация по ошибкам
Формально, когда необходимо применить поток, то Вы расширяете базовый поток и применяете один или несколько методов, в зависимости от типа потока. Например, поток Transform предполагает использование метода _transform и метода _flush опционально. На примере ниже видно, как поток Stream разбивает файл по строкам:
Со всеми “кишками” метода наружу код выглядит довольно неопрятно. Теперь Вы можете передать эти методы в качестве опций в конструктор. Просто уберите знак подчеркивания (_). Liner позволяет изменить код (с использованием нотации объекта из расширений в ES6) и получить следующее:
Ссылка на документацию: Конструктор потока
Если бы Вы прописали команду dns.lookup в ранних версиях io.js, то Вам бы выдали только первый адрес. Сейчас же есть опция {all: true}, которая позволяет получить целый массив адресов.
Ссылка на документацию: О dns.lookup
Если отклонить(reject) promise, но об этом никто никогда не узнает, можно ли считать, что его действительно отклонили? Конечно это произошло, но опять же — об этом никто не узнает! Как можно догадаться, здесь таится корень появления множества сложных ошибок в коде. Для примера обратите внимание на код ниже:
Видите ошибку? Функция delay возвращает Promise, который будет выполнен через определенное время, а когда он будет выполнен, получим undefined и ReferenceError. Это произойдет, потому что мы попытались получить доступ к свойству foo со значением undefined. Между тем, мы никогда не узнаем, что произошло, и будем с удивлением чесать в затылке, потому что на необработанные отказы никто не обратил внимание. Хотя в некоторых библиотеках, возможность такого поведения promise прописана, в ES6 Вы информации об этом не найдете. К счастью, Вы можете провести проверку кода с помощью события unhandledRejection через process.
Если Вы используете promise в ES6, то рекомендуем настроить unhandledRejection, чтобы ни один отказ не остался незамеченным.
Ссылка на документацию: process.on(“unhandledRejection”)
Отметим: Также существует событие rejectionHandled. Оно используется в том случае, когда надо разобраться с отказом promise, возникающим в следующем шаге event loop. rejectionHandled полезен, когда необходимо обнулить promise, которые были неверно отсеяны с помощью unhandledRejection. Для более детального описания смотрите документацию.
Начиная с данной версии, между потоками в C++ и JS появилась полноценная связь. Теперь Вы можете использовать стандартный Duplex поток для ввода данных при работе с потоками низкого порядка (например, c программными гнездами) на C++. Метод tls.connect () использует эту особенность в полной мере. В качестве примера просмотрите следующий набор тестов.
Далее описан удобный метод того, как проводить поиск в буфере с помощью строки, буфера или числа. Метод ведет себя так же, как и Array#indexOf, а именно: он возвращает индекс стартовой позиции первого найденного совпадения в буфере. По желанию вы можете задать стартовый индекс в качестве дополнительного параметра.
Дополнительный метод lastIndexOf пока находится в обсуждении.
Ссылка на документацию: buffer#indexOf
Теперь Вы можете предварительно загрузить модули во время выполнения скриптов или использования REPL. Сделать это можно с помощью -r или флага --require. Например:
То же самое, что и:
Предварительная загрузка модулей дает новые возможности использования io.js. Например, Вы можете добавить новую функциональность в полезное работающее приложение. Или: Вы хотите создать приложение, которое будет делать мгновенные снимки состояния неупорядоченного массива для устранения утечки данных. Для этого Вам не надо будет поддерживать такую конфигурацию:
Вам надо будет всего лишь запустить приложение с предварительно загруженным модулем тогда, когда Вам нужна требуемая функциональность:
Еще один вариант использования — вместе с компиляторами (Babel, CoffeeScript и т.п.). Например, если Вы используете Babel для компиляции кода на ES6 или ES7, необходимо прописать примерно следующее:
Теперь, Вы можете провести настройку прямо из командной строки без использования родного приложения:
Отметим: На настоящий момент, Babel уже полноценно поддерживает Node. Специальный инструмент позволяет делать описанное выше и даже больше.
Хотя Synchonous I/O удобен, в особенности для shell-скриптинга, от него сильно снижается производительность многих приложений (например, серверов). Вы можете поискать Sync в коде, однако что делать, если Вы используете сторонний модуль, который не следует схеме именования? Именно здесь на выручку приходит флаг --trace-sync-io. Когда выполняется синхронный запрос, Вам приходит уведомление о stack trace.
Давайте посмотрим на простой пример:
Имеем HTTP сервер, который обрабатывает синхронный код по каждому запросу. Рекомендовать такой вариант нельзя. Так, если мы выполним iojs --trace-sync-io server.js, а затем перейдем на httpL//localhost:3000, увидим в консоли следующее предупреждение:
Несколько интересных возможностей, на которые стоит обратить внимание:
По моему мнению лучшую информацию можно найти на GitHub в ChangeLog. Также io.js ведет официальный блог на Medium, на котором можно найти информацию о последних обновлениях платформы и о проекте в целом.
Автор: Марк Хартер (Marc Harter)
Над переводом работали: greebn9k(Сергей Грибняк), seninrom(Роман Сенин), silmarilion(Андрей Хахарев)
Singree
Платформа io.js развивается быстрыми темпами, оказывая большое влияние на всю экосистему Node. За короткое время в io.js было внесено множество исправлений, улучшена производительность, а также добавлены новые возможности и функции.
Если Вы не следили за развитием io.js, ничего страшного — в данной статье мы вкратце представим основные возможности платформы. Рассматривать их мы будет по мере выхода, то есть от старых к новым. Где это будет нужным, дадим ссылки на соответствующую документацию (на английском языке). Описывая каждую новую функциональность, будем указывать ее первую версию. Код написан с использованием стандартных функций ES6, которые доступны в io.js. Итак, начнем…
ES6 возможности (1.0.0)
Первоначально одной из задач разработчиков io.js было приблизиться к функциональности V8, а именно: полноценно использовать все имеющиеся возможности ES6. Считалось, что встроенные по умолчанию генераторы, так же, как и другие функции ES6, будут работать достаточно стабильно и без флагов. Советуем не спешить с поиском информации по ES6 — для начала ознакомьтесь с документацией, ссылки на которую мы дадим в статье.
Ссылка на документацию: Функции ES6
Отметим: По умолчанию в Node 0.12 присутствуют некоторые функции ES6. Под флагом --harmony можно найти еще больше функций. Однако, работа и доступность функций ES6 отличается от io.js (например: в Node генераторы по умолчанию не присутствуют).
Подробная документация по ошибкам (1.2.0)
Когда-нибудь задавались вопросом, что такое EPIPE, EMFILE, ENOENT? Я лично задавал себе этот вопрос и не раз. Слава богу, теперь в io.js появилась значительно улучшенная документация по ошибкам.
Ссылка на документацию: Документация по ошибкам
Простая реализация потоков (1.2.0)
Формально, когда необходимо применить поток, то Вы расширяете базовый поток и применяете один или несколько методов, в зависимости от типа потока. Например, поток Transform предполагает использование метода _transform и метода _flush опционально. На примере ниже видно, как поток Stream разбивает файл по строкам:
var stream = require('stream')
var liner = new stream.Transform( { objectMode: true } )
liner._transform = function (chunk, enc, done) {
var data = chunk.toString()
if (this._lastLineData) {
data = this._lastLineData + data
}
var lines = data.split('\n')
this._lastLineData =
lines.splice(lines.length-1,1)[0]
lines.forEach(this.push.bind(this))
done()
}
liner._flush = function (done) {
if (this._lastLineData) {
this.push(this._lastLineData)
}
this._lastLineData = null
done()
}
module.exports = liner
Со всеми “кишками” метода наружу код выглядит довольно неопрятно. Теперь Вы можете передать эти методы в качестве опций в конструктор. Просто уберите знак подчеркивания (_). Liner позволяет изменить код (с использованием нотации объекта из расширений в ES6) и получить следующее:
'use strict'
const stream = require('stream')
let liner = new stream.Transform({
// Include any existing constructor options
objectMode: true,
// This is the _transform method
transform (chunk, enc, done) {
let data = chunk.toString()
if (this._lastLineData) {
data = this._lastLineData + data
}
let lines = data.split('\n')
this._lastLineData =
lines.splice(lines.length - 1, 1)[0]
lines.forEach(this.push.bind(this))
done()
},
// This is the _flush method
flush (done) {
if (this._lastLineData) {
this.push(this._lastLineData)
}
this._lastLineData = null
done()
}
})
module.exports = liner
Ссылка на документацию: Конструктор потока
Возможность просмотра всех IP адресов для домена (1.2.0)
Если бы Вы прописали команду dns.lookup в ранних версиях io.js, то Вам бы выдали только первый адрес. Сейчас же есть опция {all: true}, которая позволяет получить целый массив адресов.
'use strict'
const dns = require('dns')
// Returns first address
dns.lookup('google.com', console.log)
// => '173.194.46.40' 4
// Returns all resolved addresses in an array
dns.lookup('google.com', { all: true }, console.log)
/* => [ { address: '173.194.46.40', family: 4 },
{ address: '173.194.46.38', family: 4 },
...
{ address: '2607:f8b0:4009:804::1007', family: 6 } ]
*/
Ссылка на документацию: О dns.lookup
Событие unhandleRejection (1.4.1)
Если отклонить(reject) promise, но об этом никто никогда не узнает, можно ли считать, что его действительно отклонили? Конечно это произошло, но опять же — об этом никто не узнает! Как можно догадаться, здесь таится корень появления множества сложных ошибок в коде. Для примера обратите внимание на код ниже:
'use strict'
let delay = function (ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms)
})
}
delay(2000)
.then(function (data) {
data.foo = 'hello'
})
Видите ошибку? Функция delay возвращает Promise, который будет выполнен через определенное время, а когда он будет выполнен, получим undefined и ReferenceError. Это произойдет, потому что мы попытались получить доступ к свойству foo со значением undefined. Между тем, мы никогда не узнаем, что произошло, и будем с удивлением чесать в затылке, потому что на необработанные отказы никто не обратил внимание. Хотя в некоторых библиотеках, возможность такого поведения promise прописана, в ES6 Вы информации об этом не найдете. К счастью, Вы можете провести проверку кода с помощью события unhandledRejection через process.
process.on('unhandledRejection', function (er) {
console.log('got unhandled rejection', er.stack)
})
Если Вы используете promise в ES6, то рекомендуем настроить unhandledRejection, чтобы ни один отказ не остался незамеченным.
Ссылка на документацию: process.on(“unhandledRejection”)
Отметим: Также существует событие rejectionHandled. Оно используется в том случае, когда надо разобраться с отказом promise, возникающим в следующем шаге event loop. rejectionHandled полезен, когда необходимо обнулить promise, которые были неверно отсеяны с помощью unhandledRejection. Для более детального описания смотрите документацию.
StreamWrap и JSStream (1.4.1)
Начиная с данной версии, между потоками в C++ и JS появилась полноценная связь. Теперь Вы можете использовать стандартный Duplex поток для ввода данных при работе с потоками низкого порядка (например, c программными гнездами) на C++. Метод tls.connect () использует эту особенность в полной мере. В качестве примера просмотрите следующий набор тестов.
Метод Buffer#indexOf
Далее описан удобный метод того, как проводить поиск в буфере с помощью строки, буфера или числа. Метод ведет себя так же, как и Array#indexOf, а именно: он возвращает индекс стартовой позиции первого найденного совпадения в буфере. По желанию вы можете задать стартовый индекс в качестве дополнительного параметра.
'use strict'
const assert = require('assert')
let buf = new Buffer('abc def ghi')
assert.equal(buf.indexOf('abc'), 0)
assert.equal(buf.indexOf('bc'), 1)
assert.equal(buf.indexOf('def'), 4)
assert.equal(buf.indexOf('c'), 2)
assert.equal(buf.indexOf('c', 4), 11)
Дополнительный метод lastIndexOf пока находится в обсуждении.
Ссылка на документацию: buffer#indexOf
Модули предварительной загрузки (1.6.0)
Теперь Вы можете предварительно загрузить модули во время выполнения скриптов или использования REPL. Сделать это можно с помощью -r или флага --require. Например:
iojs -r ./foo -r bar my-app.js
То же самое, что и:
require('./foo')
require('bar')
require('./my-app')
Предварительная загрузка модулей дает новые возможности использования io.js. Например, Вы можете добавить новую функциональность в полезное работающее приложение. Или: Вы хотите создать приложение, которое будет делать мгновенные снимки состояния неупорядоченного массива для устранения утечки данных. Для этого Вам не надо будет поддерживать такую конфигурацию:
if (cfg.useHeapdump) require('heapdump')
Вам надо будет всего лишь запустить приложение с предварительно загруженным модулем тогда, когда Вам нужна требуемая функциональность:
iojs -r heapdump app.js
Еще один вариант использования — вместе с компиляторами (Babel, CoffeeScript и т.п.). Например, если Вы используете Babel для компиляции кода на ES6 или ES7, необходимо прописать примерно следующее:
require('babel/register')
require('./my-actual-app')
Теперь, Вы можете провести настройку прямо из командной строки без использования родного приложения:
iojs -r babel/register my-actual-app.js
Отметим: На настоящий момент, Babel уже полноценно поддерживает Node. Специальный инструмент позволяет делать описанное выше и даже больше.
Устранение ошибок с помощью Synchronous I/O (2.1.0)
Хотя Synchonous I/O удобен, в особенности для shell-скриптинга, от него сильно снижается производительность многих приложений (например, серверов). Вы можете поискать Sync в коде, однако что делать, если Вы используете сторонний модуль, который не следует схеме именования? Именно здесь на выручку приходит флаг --trace-sync-io. Когда выполняется синхронный запрос, Вам приходит уведомление о stack trace.
Давайте посмотрим на простой пример:
'use strict'
const http = require('http')
const cp = require('child_process')
http.createServer(function (req, res) {
let stdout = cp.execFileSync('whoami')
res.end(`${stdout}\n`)
}).listen(3000)
Имеем HTTP сервер, который обрабатывает синхронный код по каждому запросу. Рекомендовать такой вариант нельзя. Так, если мы выполним iojs --trace-sync-io server.js, а затем перейдем на httpL//localhost:3000, увидим в консоли следующее предупреждение:
WARNING: Detected use of sync API
at spawnSync (child_process.js:1241:27)
at execFileSync (child_process.js:1291:13)
at /Users/wavded/Projects/whats-new-iojs/server.js:6:19
at emitTwo (events.js:87:13)
at emit (events.js:172:7)
at parserOnIncoming (_http_server.js:474:12)
at parserOnHeadersComplete (_http_common.js:88:23)
at socketOnData (_http_server.js:325:22)
at emitOne (events.js:77:13)
Всего понемногу
Несколько интересных возможностей, на которые стоит обратить внимание:
- Вы можете прописать require (‘./’) с помощью require (‘.’) (1.6.2).
- При использовании console.log или util.inspect, ES6 объекты Promise, Map и Set имеют красивый вид (2.0.0).
- os.tmpdir () работает одинаково хорошо на всех операционных системах (2.0.0). Ранее, некоторые операционные системы выдавали косые слэши, не говоря уже о других ошибках. Теперь Вы никогда не столкнетесь с этой проблемой.
- Множество других улучшений: парсинг строки запроса(query string) (1.6.1), меньшее использование tls памяти (2.0.0), более быстрый process.nextTick (2.0.0) и util.format для отдельных аргументов (2.1.0).
- Постепенное улучшение безопасности io.js. При обмене ключами по методу Диффи-Хеллмана, параметры должны быть не менее 1024 бит (2.1.0).
Где найти больше информации?
По моему мнению лучшую информацию можно найти на GitHub в ChangeLog. Также io.js ведет официальный блог на Medium, на котором можно найти информацию о последних обновлениях платформы и о проекте в целом.
Автор: Марк Хартер (Marc Harter)
Над переводом работали: greebn9k(Сергей Грибняк), seninrom(Роман Сенин), silmarilion(Андрей Хахарев)
Singree
Комментарии (7)
StreetStrider
18.06.2015 15:09+3Теперь Вы можете предварительно загрузить модули во время выполнения скриптов или использования REPL. Сделать это можно с помощью -r или флага --require.
Очень здравая фишка, но это не всё, что можно с этим сделать. Ещё более интересный кейс: загрузить модули и/или скрипты, выполнить их и перейти в REPL с результатами работы вglobal
. Когда-то приходил в трекер ноды с таким запросом, но был непонят. В ноде есть флаг--interactive
но он имеет контр-интуитивный смысл.
Необходимость не пропадала и я запилил для этих задач модуль repl.js и сейчас его широко использую в различных экспериментах с кодом, проверке идей и т.п.
Nadoedalo
18.06.2015 15:21А разве io.js до сих пор существует? В смысле активной разработки?
hell0w0rd
18.06.2015 15:40+2да. Пока происходит воссоединение, мир не стоит на месте. На пример недавно был релиз 2.3.0.
greebn9k Автор
18.06.2015 15:55+2github.com/nodejs/io.js
Они до 3.0 будут поддерживать его. А после мержа будут работать уже над одним продуктом.
StreetStrider
Что мне не нравится в модуле
fs
, так что это, что синхронные и асинхронные операции свалены в одном нейсмспейсе. А чтобы избежать коллизий, синхронные функции получили уродливый постфиксSync
. Я бы просто выделил в модуле суб-пространство имёнsync
и в нём все функции имели бы такие же имена, как и асинхронные. Также в дальнейшем так можно было бы добавить суб-пространство имёнpromise
и положить туда аналогичный набор функций, возвращающий промисы. Многие библиотекостроители догадались до этого манёвра, в результате мы имеем опрятное API в библиотеках glob, cp, mkdirp, touch и других.hell0w0rd
Есть библиотека bluebird, с промисами быстрее, чем в v8. Так вот там есть метод promisifyAll, позволяющий все функции с колбеками превратить в функции, возвращающие промисы и по умолчанию ко всем функциям просто добавляется Async.
Так что это просто особенности API, возможно не слишком удачного, но устоявшегося.
StreetStrider
1. Если сделать
dir(fs)
, то получим солянку из двух API, в которых зрительно придётся выделятьsync/async
функции.2. В любой подсистеме, как правило, используется одно из двух API. Для скриптов синхронное, для серверов асинхронное. Нет необходимости валить их в один модуль. Они могли бы быть в разных пространствах имён или даже в разных модулях.
3. Префиксы-постфиксы это очень грязная практика, так как в определённый момент возникает ситуация, когда для получения нужной функциональности приходится конструировать её имя. В итоге, приходится доблестно решать проблему доступа к объекту, которую мы сами создали какое-то время назад. Всегда, когда есть возможность, лучше пользоваться языковыми средствами, например пространствами имён.