Оригинал: New io.js Features You May Not Be Using

Платформа 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)

Всего понемногу

Несколько интересных возможностей, на которые стоит обратить внимание:
  1. Вы можете прописать require (‘./’) с помощью require (‘.’) (1.6.2).
  2. При использовании console.log или util.inspect, ES6 объекты Promise, Map и Set имеют красивый вид (2.0.0).
  3. os.tmpdir () работает одинаково хорошо на всех операционных системах (2.0.0). Ранее, некоторые операционные системы выдавали косые слэши, не говоря уже о других ошибках. Теперь Вы никогда не столкнетесь с этой проблемой.
  4. Множество других улучшений: парсинг строки запроса(query string) (1.6.1), меньшее использование tls памяти (2.0.0), более быстрый process.nextTick (2.0.0) и util.format для отдельных аргументов (2.1.0).
  5. Постепенное улучшение безопасности io.js. При обмене ключами по методу Диффи-Хеллмана, параметры должны быть не менее 1024 бит (2.1.0).

Где найти больше информации?

По моему мнению лучшую информацию можно найти на GitHub в ChangeLog. Также io.js ведет официальный блог на Medium, на котором можно найти информацию о последних обновлениях платформы и о проекте в целом.

Автор: Марк Хартер (Marc Harter)
Над переводом работали: greebn9k(Сергей Грибняк), seninrom(Роман Сенин), silmarilion(Андрей Хахарев)
Singree

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


  1. StreetStrider
    18.06.2015 15:03
    +3

    Что мне не нравится в модуле fs, так что это, что синхронные и асинхронные операции свалены в одном нейсмспейсе. А чтобы избежать коллизий, синхронные функции получили уродливый постфикс Sync. Я бы просто выделил в модуле суб-пространство имён sync и в нём все функции имели бы такие же имена, как и асинхронные. Также в дальнейшем так можно было бы добавить суб-пространство имён promise и положить туда аналогичный набор функций, возвращающий промисы. Многие библиотекостроители догадались до этого манёвра, в результате мы имеем опрятное API в библиотеках glob, cp, mkdirp, touch и других.


    1. hell0w0rd
      18.06.2015 15:40
      +2

      Есть библиотека bluebird, с промисами быстрее, чем в v8. Так вот там есть метод promisifyAll, позволяющий все функции с колбеками превратить в функции, возвращающие промисы и по умолчанию ко всем функциям просто добавляется Async.
      Так что это просто особенности API, возможно не слишком удачного, но устоявшегося.


      1. StreetStrider
        18.06.2015 15:51

        особенности API, … не слишком удачного, но устоявшегося
        Об этом я и веду речь.

        1. Если сделать dir(fs), то получим солянку из двух API, в которых зрительно придётся выделять sync/async функции.
        2. В любой подсистеме, как правило, используется одно из двух API. Для скриптов синхронное, для серверов асинхронное. Нет необходимости валить их в один модуль. Они могли бы быть в разных пространствах имён или даже в разных модулях.
        3. Префиксы-постфиксы это очень грязная практика, так как в определённый момент возникает ситуация, когда для получения нужной функциональности приходится конструировать её имя. В итоге, приходится доблестно решать проблему доступа к объекту, которую мы сами создали какое-то время назад. Всегда, когда есть возможность, лучше пользоваться языковыми средствами, например пространствами имён.


  1. StreetStrider
    18.06.2015 15:09
    +3

    Теперь Вы можете предварительно загрузить модули во время выполнения скриптов или использования REPL. Сделать это можно с помощью -r или флага --require.
    Очень здравая фишка, но это не всё, что можно с этим сделать. Ещё более интересный кейс: загрузить модули и/или скрипты, выполнить их и перейти в REPL с результатами работы в global. Когда-то приходил в трекер ноды с таким запросом, но был непонят. В ноде есть флаг --interactive но он имеет контр-интуитивный смысл.

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


  1. Nadoedalo
    18.06.2015 15:21

    А разве io.js до сих пор существует? В смысле активной разработки?


    1. hell0w0rd
      18.06.2015 15:40
      +2

      да. Пока происходит воссоединение, мир не стоит на месте. На пример недавно был релиз 2.3.0.


    1. greebn9k Автор
      18.06.2015 15:55
      +2

      github.com/nodejs/io.js

      Они до 3.0 будут поддерживать его. А после мержа будут работать уже над одним продуктом.