В этой статье мы попробуем решить реальную проблему при помощи Node.js Stream и чуточку Reactive Programming. В последнем не уверен – RP, в какой-то мере, "жупел" (как перевести buzzword?) о котором все говорят, но никто не "делает". Статья рассматривает практический пример и ориентирована на знакомого с платформой читателя, по-этому намеренно не объясняет базовые понятия – если что-то непонятно по Stream API, то стоит обратится в документацию платформы или в какой-нибудь ее пересказ (например, этот).  Начнем с описания проблемы: нам нужно построить “паука” который заберет все данные с “чужого” REST API, как-то их обработает и запишет в “нашу” базу данных. Для удобства моделирования мы опустим детали о конкретных API и базы данных (в реальности, это было API одного известного стартапа связанного с гостиницами и Postgres база данных). Представим что у нас есть две функции (код функций как и весь код из статьи можно найти тут): Мы намеренно проигнорируем обработку ошибок возможных при работе с API и базой, для простоты. Если возникнет интерес, то рассмотрим их в отдельной статье. Ну и для того чтобы было не скучно скажем что наш заказчик извращенец и он поставил следующую задачу: мы не хотим видеть у себя в база все сущности id которых содержит число 3. А сущности id которых содержат число 9 хотим дополнить текущим значением timestamp: {id: 9} -> {id: 9, timestamp: 1490571732068}. Чуть притянуто за уши, но похоже на задачи обработки и фильтрации, которые приходится решать в подобных “пауках”. Ну что же – начнем. Давайте попробуем решить данную задачу “в лоб”. Скорее всего мы закончим с кодом чем-то похожим на этот: Что не так с данным кодом? Как вы уже догадались, данную задачу легко решить при помощи Streams. Для начала разобъем эту задачу на две подзадачи: чтение и запись. Начнем с чтения, давайте попробуем выполнить наш ReadableStream: Выглядит чуть более громоздким. Стоит обратить внимание на objectMode: true – мы хотим оперировать объектами, а значит стоит передать этот флаг конструктору. Окей, теперь перейдем к записи. Имплементируем наш Writable stream. Что-то вроде этого: На что стоит обратить внимание: Ну и теперь используем наш код вот так: Как мне кажется – это очень круто, теперь из кода предельно ясно что мы читаем из одного места и пишем в другое. Кроме того читатель может проверить что наш код работает очень эффективно и использует буфер. Ну и всякие мелкие плюшки вроде того что он не блокирует event-loop. Хм –, спросит внимательный читатель, – а что же с обрабоктой данных? Для этого мы можем написать еще один Transform stream, но это как-то "плоско и скучно", по-этому мы используем библиотеку Highland.js которая позволит нам применить всеми любимые filter и map над элементами нашего "потока" сущностей. Вообще, Highland это что-то больше чем этот простой usecase, но это тема отдельной и не маленькой статьи. Как-то так: Как по мне, очень похоже на операции со списками и читаемо. А .flatten() и .batchWithTimeOrCount(100, 1000) нужны нам только потому что наши Streamы оперирует массивами вместо отдельных объектов. Вот сообственно и все. Надеюсь я достиг своей цели и заинтересовал читателя в изучении Stream и Highland.js.
Перевод этой статьи на английский