В чем разница между BehaviorSubject и Observable?
Я изучаю паттерны проектирования в RxJS и не понимаю разницу между BehaviorSubject
и Observable
.
Насколько я понимаю, BehaviorSubject
может содержать значение, которое может меняться. На него можно подписаться, и подписчики могут получать обновленные значения. Оба, кажется, имеют совершенно одинаковую назначение.
- Когда следует использовать
Observable
, а когдаBehaviorSubject
, и наоборот? - В чем преимущества использования
BehaviorSubject
по сравнению сObservable
и наоборот?
5 ответ(ов)
BehaviorSubject
— это вариант Subject
, тип Observable
, к которому можно "подписываться", как к любому другому Observable.
Особенности BehaviorSubject
- Необходим начальный значение, так как он всегда должен возвращать значение при подписке, даже если метод
next()
не был вызван. - При подписке он возвращает последнее значение Subject. Обычный Observable активируется только при получении метода
onNext()
. - В любой момент можно получить последнее значение Subject с помощью метода
getValue()
, не используя Observable.
Особенности Subject
- Subject — это "наблюдатель" в дополнение к тому, что он является Observable; поэтому можно отправлять значения в Subject, подписываясь на него.
- Можно получить значение из BehaviorSubject, используя метод
asObservable()
.
Пример 1 с использованием BehaviorSubject
// BehaviorSubject.
// 'A' — это начальное значение. Если произойдет подписка
// после этого, она немедленно получит значение 'A'.
beSubject = new BehaviorSubject('a');
beSubject.next('b');
beSubject.subscribe(value => {
console.log('Подписка получила значение ', value);
// Подписка получила B. Это не произойдет
// для Observable или Subject по умолчанию.
});
beSubject.next('c'); // Подписка получила C.
beSubject.next('d'); // Подписка получила D.
Пример 2 с использованием Subject
// Subject.
subject = new Subject();
subject.next('b');
subject.subscribe(value => {
console.log('Подписка получила значение ', value);
// Подписка на данном этапе ничего не получит.
});
subject.next('c'); // Подписка получила C.
subject.next('d'); // Подписка получила D.
Observable можно создать как из Subject
, так и из BehaviorSubject
; например, subjectName.asObservable()
.
Единственное отличие в том, что нельзя отправлять значения в Observable с помощью метода next()
.
В Angular рекомендуется использовать BehaviorSubject
для передачи данных, так как сервис часто инициализируется до компонента.
BehaviorSubject гарантирует, что компонент, использующий сервис, получит последние обновленные данные, даже если новых обновлений не поступало, благодаря подписке компонента на сервис.
Observable: Разные результаты для каждого Observer
Одно очень важное отличие. Поскольку Observable — это просто функция, у нее нет состояния, поэтому для каждого нового Observer она выполняет код создания Observable снова и снова. Это приводит к тому, что:
Код выполняется для каждого наблюдателя. Если это HTTP-запрос, он вызывается для каждого наблюдателя.
Это может вызвать серьезные ошибки и неэффективности.
BehaviorSubject (или Subject) хранит информацию о наблюдателях, выполняет код только один раз и выдает результат всем наблюдателям.
Пример:
JSBin: http://jsbin.com/qowulet/edit?js,console
// --- Observable ---
let randomNumGenerator1 = Rx.Observable.create(observer => {
observer.next(Math.random());
});
let observer1 = randomNumGenerator1
.subscribe(num => console.log('observer 1: ' + num));
let observer2 = randomNumGenerator1
.subscribe(num => console.log('observer 2: ' + num));
// ------ BehaviorSubject/ Subject
let randomNumGenerator2 = new Rx.BehaviorSubject(0);
randomNumGenerator2.next(Math.random());
let observer1Subject = randomNumGenerator2
.subscribe(num => console.log('observer subject 1: ' + num));
let observer2Subject = randomNumGenerator2
.subscribe(num => console.log('observer subject 2: ' + num));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>
Вывод:
"observer 1: 0.7184075243594013"
"observer 2: 0.41271850211336103"
"observer subject 1: 0.8034263165479893"
"observer subject 2: 0.8034263165479893"
Обратите внимание, как использование Observable.create
создало разные выводы для каждого наблюдателя, в то время как BehaviorSubject
дал одинаковый вывод для всех наблюдателей. Это очень важно.
Другие различия можно резюмировать следующим образом:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Observable ┃ BehaviorSubject/Subject ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Просто функция, без состояния ┃ Имеет состояние. Хранит данные в ┃
┃ ┃ памяти ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Код выполняется для каждого ┃ Один и тот же код выполняется ┃
┃ наблюдателя ┃ всего один раз для всех наблюдателей ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Создает только Observable ┃ Может создавать и слушать Observable┃
┃ (только производитель данных) ┃ (производитель и потребитель данных)┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Использование: Простой Observable ┃ Использование: ┃
┃ с одним наблюдателем. ┃ * Хранение и частое изменение ┃
┃ ┃ * Несколько наблюдателей слушают данные ┃
┃ ┃ * Прокси между Observable и ┃
┃ ┃ Observer ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Объект Observable представляет собой коллекцию, основанную на механизме «push».
Интерфейсы Observer и Observable предоставляют обобщенный механизм для уведомлений на основе push, также известный как паттерн проектирования «наблюдатель». Объект Observable является тем объектом, который отправляет уведомления (поставщик); объект Observer представляет класс, который их получает (наблюдатель).
Класс Subject наследует как Observable, так и Observer, в том смысле, что он является и наблюдателем, и наблюдаемым объектом. Вы можете использовать Subject для подписки всех наблюдателей, а затем подписать сам Subject на источник данных в бэкенде.
var subject = new Rx.Subject();
var subscription = subject.subscribe(
function (x) { console.log('onNext: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('onCompleted'); });
subject.onNext(1);
// => onNext: 1
subject.onNext(2);
// => onNext: 2
subject.onCompleted();
// => onCompleted
subscription.dispose();
Дополнительную информацию вы можете найти по ссылке: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/subjects.md
В вашем вопросе затрагивается важный момент: при преобразовании BehaviorSubject
в Observable
с помощью метода asObservable
, он по-прежнему сохраняет поведение, при котором при подписке возвращается последнее значение. Это может вызвать путаницу, особенно когда библиотеки экспонируют поля как Observable
, но на самом деле используют за кулисами такие классы, как Subject
или BehaviorSubject
.
В приведенном вами примере:
let A = new Rx.Subject();
let B = new Rx.BehaviorSubject(0);
A.next(1);
B.next(1);
A.asObservable().subscribe(n => console.log('A', n));
B.asObservable().subscribe(n => console.log('B', n));
A.next(2);
B.next(2);
Тут A
является обычным Subject
, который ничего не выдаст подписчику, пока не будет передано какое-либо значение после подписки, в то время как B
, являясь BehaviorSubject
, при подписке сразу выдаст последнее значение, которому было присвоено (в данном случае это 0
, так как BehaviorSubject
инициализирован с этим значением).
Это очень важно учитывать, поскольку если вы подписываетесь на Observable
, созданный из Subject
, и не получаете значения при подписке, это может оказать влияние на ваше понимание работы потоков данных и вести к ошибкам при отладке. Будьте осторожны в выборе подходящего типа в зависимости от того, какое поведение вам необходимо.
Представьте себе Observable как трубу, в которой течет вода: иногда вода течет, а иногда нет. В некоторых случаях вам может понадобиться труба, в которой вода всегда присутствует, независимо от ее количества. Для этого вы можете создать специальную трубу, которая всегда содержит воду, как бы мала она ни была. Давайте назовем эту специальную трубу BehaviorSubject. Если вы являетесь поставщиком воды в вашем сообществе, вы можете спокойно спать по ночам, зная, что ваша новая труба работает безотказно.
В техническом смысле: вы можете столкнуться с ситуациями, когда Observable должен всегда иметь значение. Возможно, вы хотите отслеживать значение текстового ввода со временем, в таком случае вы можете создать экземпляр BehaviorSubject, чтобы обеспечить такое поведение. Например:
const firstNameChanges = new BehaviorSubject("<пусто>");
// передаем изменения значений.
firstNameChanges.next("Jon");
firstNameChanges.next("Arya");
Теперь вы можете использовать "value", чтобы отслеживать изменения во времени:
firstNameChanges.value;
Это будет полезно, когда вы будете комбинировать Observable позже, так как зная, что ваш поток является BehaviorSubject, вы можете гарантировать, что поток сработает или сигнализирует хотя бы один раз.
Angular: условный класс с *ngClass
В чем разница между String.slice и String.substring?
Проверка соответствия строки регулярному выражению в JS
Существует ли ссылка на "последнюю" библиотеку jQuery в Google APIs?
Как создать диалог с кнопками "Ок" и "Отмена"