#rxjs #observable #unsubscribe
Вопрос:
Я попытался отказаться от подписки в рамках метода подписки. Похоже, это работает, я не нашел в Интернете примера, чтобы вы могли сделать это таким образом.
Я знаю, что есть много других возможностей отказаться от подписки на метод или ограничить его с помощью каналов. Пожалуйста, не предлагайте никакого другого решения, но ответьте, почему вы не должны этого делать или это возможный способ ?
пример:
let localSubscription = someObservable.subscribe(result => {
this.result = resu<
if (localSubscription amp;amp; someStatement) {
localSubscription.unsubscribe();
}
});
Комментарии:
1. причина, по которой этого не следует делать, заключается в том, что, как правило, это анти-шаблон для управления подписками внутри подписки. анти-шаблоны могут привести к непреднамеренным побочным эффектам или трудному для понимания коду. как и в этом случае , может быть трудно определить, когда именно произойдет или должна произойти отмена подписки, в зависимости от того, что происходит
someStatement
, особенно если у вас несколько конкурирующих потоков событий, в то время как использование оператора может сделать это более ясным или определенным. например, может ли этот код быть чище с использованием atakeWhile(() => someStatement)
?
Ответ №1:
Проблема
Иногда шаблон, который вы использовали выше, будет работать, а иногда нет. Вот два примера, вы можете попробовать запустить их самостоятельно. Один выдаст ошибку, а другой-нет.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
Вывод:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Что-то похожее:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
Вывод:
1
2
3
4
На этот раз вы не получите ошибку, но вы также отписались до того, как 5 были отправлены из наблюдаемого источника of(1,2,3,4,5)
Скрытые ограничения
Если вы знакомы с планировщиками в RxJS, вы можете сразу же обнаружить дополнительную скрытую информацию, которая позволяет одному примеру работать, а другому-нет.
delay
(Даже задержка в 0 миллисекунд) возвращает наблюдаемое, использующее асинхронный планировщик. Это означает, по сути, что текущий блок кода завершит выполнение до того, как отложенное наблюдаемое получит возможность выдать.
Это гарантирует, что в однопоточной среде (например, в среде выполнения Javascript, используемой в настоящее время в браузерах) ваша подписка была инициализирована.
Решения
1. Сохраняйте хрупкую кодовую базу
Одно из возможных решений-просто игнорировать здравый смысл и продолжать использовать этот шаблон для отказа от подписки. Для этого вы и все члены вашей команды, которые могут использовать ваш код для справки или которым когда-нибудь понадобится поддерживать ваш код, должны взять на себя дополнительную когнитивную нагрузку по запоминанию того, какие наблюдаемые используют правильный планировщик.
Изменение способа преобразования наблюдаемых данных в одной части приложения может привести к неожиданным ошибкам в каждой части приложения, которая полагается на эти данные, предоставляемые асинхронным планировщиком.
Например: код, который работает нормально при запросе сервера, может сломаться, когда синхронно возвращается обналиченный результат. То, что кажется оптимизацией, теперь наносит ущерб вашей кодовой базе. Когда появляется ошибка такого рода, источник может быть довольно трудно отследить.
Наконец, если когда-либо браузеры (или вы запускаете код в Node.js) начните поддерживать многопоточные среды, вашему коду придется либо обходиться без этого улучшения, либо быть переписанным заново.
2. Сделать «отписку внутри обратного вызова подписки» безопасным шаблоном
Идиоматический код RxJS пытается быть агностиком, когда это возможно.
Вот как вы можете использовать приведенный выше шаблон, не беспокоясь о том, какой планировщик использует наблюдаемый. Это фактически не зависит от планировщика, хотя, вероятно, усложняет довольно простую задачу намного больше, чем это необходимо.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
Это позволяет безопасно использовать шаблон «отказаться от подписки внутри подписки». Это всегда будет работать независимо от планировщика и будет продолжать работать, если (например) вы поместите свой код в многопоточную среду ( delay
приведенный выше пример может сломаться, но этого не произойдет).
3. Операторы RxJS
Лучшими решениями будут те, которые используют операторов, которые обрабатывают подписку/отмену подписки от вашего имени. Они не требуют дополнительной когнитивной нагрузки в наилучших обстоятельствах и относительно хорошо справляются с ошибками (менее пугающие действия на расстоянии) в более экзотических обстоятельствах.
Большинство операторов более высокого порядка делают это ( concat
, merge
, concatMap
, switchMap
, mergeMap
, и т. Д.). Другие операторы, такие как take
, takeUntil
, takeWhile
, ect, позволяют использовать более декларативный стиль для управления подписками.
Там, где это возможно, они предпочтительнее, поскольку все они с меньшей вероятностью вызовут странные ошибки или путаницу в команде, которая их использует.
Приведенные выше примеры переписаны заново:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
Комментарии:
1. Вау, большое вам спасибо за подробные объяснения и примеры. очень хороший ответ.
Ответ №2:
Это рабочий метод, но RxJS в основном рекомендует использовать async
трубу в угловой. Это идеальное решение. В вашем примере вы присваиваете result
объекту свойство, и это не очень хорошая практика.
Если вы используете свою переменную в шаблоне, то просто используйте async
pipe. Если вы этого не сделаете, просто сделайте это заметным таким образом:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
И тогда вы можете использовать свой результат$ в тех случаях, когда он вам нужен: в другом наблюдаемом или шаблоне.
Также вы можете использовать pipe(take(1))
или pipe(first())
отказаться от подписки. Существуют также некоторые другие методы, позволяющие отказаться от подписки без дополнительного кода.
Ответ №3:
Существуют различные способы отмены подписки на данные:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = resu<
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = resu<
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = resu<
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = resu<
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = resu<
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
Лично я бы предпочел метод 4, потому что вы можете использовать одну и ту же тему для нескольких подписок, если у вас есть одна страница.