Потоки Node.js проти спостережуваних


77

Після вивчення спостережуваних , я знаходжу їх дуже схожі на Node.js потоки . Обидва мають механізм сповіщення споживача, коли надходять нові дані, виникає помилка або більше немає даних (EOF).

Я хотів би дізнатись про концептуальні / функціональні відмінності між ними. Дякую!


1
@BenjaminGruenbaum Цікаво, чому ви позначили це тегами rxjs та беконом? OP , здається, відноситься до спостережуваних з ECMAScript-гармонії
Берги

@Bergi попередні знання про ОП та питання. В основному.
Бенджамін Груенбаум,

1
Лол вітає з прихильниками, але я не уявляю, чому це питання не закрито. Як це справжнє запитання / підходить для SO.
Олександр Міллс

4
@AlexanderMills, як це не відповідне питання для SO? Це не питання "що є вашим улюбленим"; він запитує різницю між двома часто використовуваними реактивними схемами в JS / Node.
Michael Martin-Smucker

Відповіді:


101

І 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 віддає перевагу підкласуванню, замість того, щоб зв’язувати Readables та застосовувати функції. ObservableНаприклад, на моделі, перетворення значень відповідає застосуванню функції трансформатора до потоку. Це не вимагає нового підтипу Transform.

Observable.just = function(/*... arguments*/) {
    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, і це працює досить добре.


5
Я справді не впевнений у цілому цьому "розширенні до Ecmascript". RxJS - це просто бібліотека, те саме з RxJava тощо. Зрештою, в ES7 або ES8 можуть бути деякі ключові слова в ES / JS, які стосуються Observables, але вони, звичайно, не є частиною мови, і, звичайно, не коли ви відповіли на запитання у 2015 році
Олександр Міллс

1
Чи підтримує реалізація RX зворотний тиск без втрат? Наприклад, якщо nodejs читає потік у призупиненому режимі, ми можемо використовувати read()метод для читання з потоку на вимогу. І drain eventможе сигналізувати про те, що запис, який можна записати, може отримувати більше даних.
Баггі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.