І Observables, і потоки node.js дозволяють вирішити одну і ту ж основну проблему: асинхронно обробляти послідовність значень. Я вважаю, що основна різниця між ними пов’язана з контекстом, який спонукав її появу. Цей контекст відображений у термінології та API.
На стороні Observables у вас є розширення EcmaScript, яке вводить модель реактивного програмування. Він намагається заповнити прогалину між генерацією вартості та асинхронністю мінімалістичними та складовими концепціями Observer
та Observable
.
На стороні node.js та Streams ви хотіли створити інтерфейс для асинхронної та ефективної обробки мережевих потоків та локальних файлів. У термінології походить від цього початкового контексту , і ви отримаєте pipe
, chunk
, encoding
, flush
, Duplex
, Buffer
і т.д. Маючи практичний підхід , який забезпечує явну підтримку для конкретних випадків використання ви втрачаєте деяку здатність складати речі , тому що це не так форми. Наприклад, ви використовуєте push
на Readable
потоці та write
на, Writable
хоча, концептуально, ви робите те саме: публікуєте значення.
Отже, на практиці, якщо ви подивитесь на концепції та якщо ви використовуєте опцію { objectMode: true }
, ви можете зіставити Observable
з Readable
потоком і Observer
з Writable
потоком. Ви навіть можете створити кілька простих адаптерів між двома моделями.
var Readable = require('stream').Readable;
var Writable = require('stream').Writable;
var util = require('util');
var Observable = function(subscriber) {
this.subscribe = subscriber;
}
var Subscription = function(unsubscribe) {
this.unsubscribe = unsubscribe;
}
Observable.fromReadable = function(readable) {
return new Observable(function(observer) {
function nop() {};
var nextFn = observer.next ? observer.next.bind(observer) : nop;
var returnFn = observer.return ? observer.return.bind(observer) : nop;
var throwFn = observer.throw ? observer.throw.bind(observer) : nop;
readable.on('data', nextFn);
readable.on('end', returnFn);
readable.on('error', throwFn);
return new Subscription(function() {
readable.removeListener('data', nextFn);
readable.removeListener('end', returnFn);
readable.removeListener('error', throwFn);
});
});
}
var Observer = function(handlers) {
function nop() {};
this.next = handlers.next || nop;
this.return = handlers.return || nop;
this.throw = handlers.throw || nop;
}
Observer.fromWritable = function(writable, shouldEnd, throwFn) {
return new Observer({
next: writable.write.bind(writable),
return: shouldEnd ? writable.end.bind(writable) : function() {},
throw: throwFn
});
}
Можливо, ви помітили, що я змінив декілька назв і використав простіші концепції Observer
та Subscription
, введені тут, щоб уникнути перевантаження відповідальності, зробленої Observables в Generator
. В основному, програма Subscription
дозволяє скасувати підписку на Observable
. У будь-якому випадку, з наведеним вище кодом ви можете мати pipe
.
Observable.fromReadable(process.stdin).subscribe(Observer.fromWritable(process.stdout));
Порівняно з тим process.stdin.pipe(process.stdout)
, що у вас є, це спосіб комбінувати, фільтрувати та трансформувати потоки, який також працює для будь-якої іншої послідовності даних. Ви можете досягти цього за допомогою Readable
, Transform
та Writable
потоків, але API віддає перевагу підкласуванню, замість того, щоб зв’язувати Readable
s та застосовувати функції. Observable
Наприклад, на моделі, перетворення значень відповідає застосуванню функції трансформатора до потоку. Це не вимагає нового підтипу Transform
.
Observable.just = function() {
var values = arguments;
return new Observable(function(observer) {
[].forEach.call(values, function(value) {
observer.next(value);
});
observer.return();
return new Subscription(function() {});
});
};
Observable.prototype.transform = function(transformer) {
var source = this;
return new Observable(function(observer) {
return source.subscribe({
next: function(v) {
observer.next(transformer(v));
},
return: observer.return.bind(observer),
throw: observer.throw.bind(observer)
});
});
};
Observable.just(1, 2, 3, 4, 5).transform(JSON.stringify)
.subscribe(Observer.fromWritable(process.stdout))
Висновок? Легко представити реактивну модель та Observable
концепцію де завгодно. Важче реалізувати цілу бібліотеку навколо цієї концепції. Усі ці маленькі функції повинні працювати послідовно. Зрештою, проект ReactiveX все ще триває. Але якщо вам дійсно потрібно надіслати вміст файлу клієнту, зайнятися кодуванням та заархівувати його, тоді підтримка там є, в NodeJS, і це працює досить добре.