Чи потрібно скасувати підписку на спостереження, створені методами Http?


211

Вам потрібно скасувати підписку на виклики Angular 2 http, щоб запобігти витоку пам'яті?

 fetchFilm(index) {
        var sub = this._http.get(`http://example.com`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilm(json));
            })
            .subscribe(e=>sub.unsubscribe());
            ...


1
Дивіться stackoverflow.com/questions/34461842/… (і коментарі також)
Ерік Мартінес

Відповіді:


253

Отже, відповідь - ні, ви цього не робите. Ng2очистить його сам.

Джерело служби Http від джерела резервного Http XHR Angular:

введіть тут опис зображення

Зверніть увагу, як воно працює complete()після отримання результату. Це означає, що він фактично скасовує підписку після завершення. Тож вам не потрібно робити це самостійно.

Ось тест на перевірку:

  fetchFilms() {
    return (dispatch) => {
        dispatch(this.requestFilms());

        let observer = this._http.get(`${BASE_URL}`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilms(json.results));
                dispatch(this.receiveNumberOfFilms(json.count));
                console.log("2 isUnsubscribed",observer.isUnsubscribed);
                window.setTimeout(() => {
                  console.log("3 isUnsubscribed",observer.isUnsubscribed);
                },10);
            })
            .subscribe();
        console.log("1 isUnsubscribed",observer.isUnsubscribed);
    };
}

Як і очікувалося, ви бачите, що після отримання результату та закінчення роботи з операторами, що спостерігаються, він завжди автоматично підписується. Це відбувається в тайм-аут (№3), тому ми можемо перевірити стан спостережуваних, коли це все зроблено та завершено.

І результат

введіть тут опис зображення

Отже, жодної протікання не було б як Ng2автоматична підписка!

Приємно зазначити: Це Observableкласифікується так, що finite, всупереч тому, infinite Observableщо нескінченний потік даних може випромінюватися, як, наприклад, clickслухач DOM .

ДЯКУЙТЕ, @rubyboy за допомогу в цьому.


17
що відбувається у випадках, коли користувач залишає сторінку до того, як відповідь надходить, або якщо відповідь ніколи не надходить до того, як користувач покине перегляд? Це не призвело б до витоку?
Thibs

1
Я також хотів би знати вищезазначене.
1984.

4
@ 1984 Відповідь ВЖЕ ЗАЯВЛЯЙТЕ. Ця відповідь цілком неправильна саме з тієї причини, яку висунув цей коментар. Користувач, який виїжджає, - це посилання на пам'ять. Плюс, якщо код у цій підписці працює після відходу користувача, це може спричинити помилки / несподівані побічні ефекти. Я схильний використовувати takeWhile (() => this.componentActive) для всіх спостережуваних даних і просто встановити this.componentActive = false в ngOnDestroy для очищення всіх спостережуваних компонентів.
Ленні

4
Приклад, показаний тут, охоплює глибокі кутові приватні api. з будь-якими іншими приватними api, вони можуть змінюватися оновленням без попереднього повідомлення. Правило: якщо це не підтверджено офіційно, не робіть припущень. Наприклад, AsynPipe має чітку документацію про те, що він автоматично підписується / скасовує передплату. Документи HttpClient про це нічого не згадують.
YoussefTaghlabi

1
@YoussefTaghlabi, FYI, в офіційній кутовій документації ( angular.io/guide/http ) зазначається, що: "AsyncPipe підписується (і скасовує підписки) для вас автоматично." Отже, це дійсно офіційно задокументовано.
Худжі

96

Про що ви говорите люди !!!

Гаразд, тому є дві причини скасувати підписку на спостереження. Здається, ніхто не дуже багато говорить про дуже важливу другу причину!

1) Очищення ресурсів. Як говорили інші, це є мізерною проблемою для спостереження HTTP. Це просто прибереться.

2) Не допускайте subscribeзапуску обробника.

(Для HTTP це фактично також скасує запит у веб-переглядачі, тому він не витратить часу на читання відповіді. Але це насправді відміна мого основного пункту нижче.)

Релевантність номера 2 залежатиме від того, що робить ваш обробник підписки:

Якщо у вашої subscribe()функції обробника є будь-який побічний ефект, який небажаний, якщо незалежно від виклику він закритий або розпоряджений, ви повинні скасувати підписку (або додати умовну логіку), щоб запобігти його виконанню.

Розглянемо кілька випадків:

1) Форма входу. Ви вводите ім'я користувача та пароль та натискаєте "Увійти". Що робити, якщо сервер повільний, і ви вирішите натиснути Escape, щоб закрити діалогове вікно? Ви, ймовірно, припустите, що ви не ввійшли в систему, але якщо http-запит повернувся після натискання клавіші escape, ви все одно будете виконувати будь-яку логіку у вас. Це може призвести до перенаправлення на сторінку облікового запису, встановлення небажаного файлу cookie або змінної маркера. Мабуть, це не те, чого очікував ваш користувач.

2) Форма "надіслати електронну пошту".

Якщо subscribe обробник для "sendEmail" робить щось подібне до запуску анімації "Ваш електронний лист надсилається", перенесіть вас на іншу сторінку або намагається отримати доступ до будь-якого розпорядження, ви можете отримати винятки або небажану поведінку.

Також будьте обережні, щоб не припустити, що unsubscribe()означає «скасувати». Після того, як повідомлення HTTP перебуває у польоті unsubscribe(), НЕ скасовує HTTP-запит, якщо воно вже надійшло до вашого сервера. Це лише скасує відповідь, що повернеться до вас. І електронний лист, ймовірно, буде надісланий.

Якщо ви створюєте підписку для надсилання електронної пошти безпосередньо всередині компонента інтерфейсу, ви, ймовірно, хочете скасувати підписку на розпорядження, але якщо електронний лист надсилається централізованою службою, що не користується інтерфейсом, вам, ймовірно, не потрібно.

3) Кутовий компонент, який руйнується / закривається. Будь-які http-спостереження, які все ще запущені в цей час, завершать і запускають свою логіку, якщо ви не скасуєте підписку наonDestroy() . Будь-які наслідки банальні чи ні, залежатиме від того, що ви робите в обробнику підписки. Якщо ви спробуєте оновити щось, що більше не існує, ви можете отримати помилку.

Іноді у вас можуть бути якісь дії, які ви хотіли б, якщо компонент розміщений, а деякі ви не зробили б. Наприклад, можливо, у вас є звуковий сигнал, що надісланий електронною поштою. Ви, мабуть, хочете, щоб це відтворювалося, навіть якщо компонент був закритий, але якщо ви спробуєте запустити анімацію на компоненті, це не вдасться. У такому випадку рішенням додаткової умовної логіки всередині підписки буде рішення - і ви НЕ хочете скасувати підписку на http-спостережуваний.

Отже, відповідаючи на власне питання, ні, вам цього не потрібно робити, щоб уникнути витоку пам'яті. Але робити це потрібно (часто), щоб уникнути небажаних побічних ефектів, запущених кодом, який може кидати винятки або пошкоджувати стан вашої програми.

Порада: Subscriptionмістить closedбулева властивість, яка може бути корисною у складних випадках. Для HTTP це буде встановлено після його завершення. У Angular може бути корисно в деяких ситуаціях встановити _isDestroyedвластивість, в ngDestroyякій може перевірити ваш subscribeобробник.

Порада 2: Якщо обробляти декілька підписок, ви можете створити спеціальний new Subscription()об’єкт та add(...)будь-які інші підписки на нього, тож коли ви скасуєте підписку на основну, він також скасує підписку на всі додані підписки.


2
Також зауважте, що якщо у вас є служба, яка повертає необроблений http, який можна отримати, і ви передаєте його перед передплатою, тоді вам потрібно лише скасувати підписку на остаточне спостережуване, а не на основне http-спостережуване. Насправді ви навіть не будете мати передплату на http безпосередньо, тому не можете.
Simon_Weaver

Незважаючи на те, що скасування передплати скасовує запит браузера, сервер все ще діє на запит. Чи є спосіб інтимного сервера перервати операцію? Я швидко надсилаю http-запити, більшість з яких скасовується підпискою. Але сервер все ще працює над скасованими клієнтом запитами, внаслідок чого законний запит чекає.
бала

@bala для цього вам доведеться придумати свій власний механізм - який би не мав нічого спільного з RxJS. Наприклад, ви можете просто помістити запити в таблицю і запустити їх через 5 секунд у фоновому режимі, тоді, якщо щось потрібно скасувати, ви просто видалите або встановите прапор на старих рядках, що зупинить їх виконання. Цілком буде залежати від того, якою є ваша заявка. Однак, оскільки ви згадуєте про блокування сервера, він може бути налаштований на дозвіл лише одного запиту, але це знову залежатиме від того, що ви використовували.
Simon_Weaver

🔥🔥🔥Порада 2 - це ПРО-ПОРАДА, 🔥🔥🔥 для всіх, хто хоче скасувати підписку на багаторазову підписку, зателефонувавши лише на одну функцію. Додайте за допомогою методу .add () та, ніж .unsubscribe () у ngDestroy.
abhay tripathi

23

Виклик unsubscribeметоду - це скоріше скасування поточного HTTP-запиту, оскільки цей метод викликає посилання abortна базовий об'єкт XHR та видаляє слухачів щодо подій завантаження та помилок:

// From the XHRConnection class
return () => {
  _xhr.removeEventListener('load', onLoad);
  _xhr.removeEventListener('error', onError);
  _xhr.abort();
};

Це сказав, що unsubscribeвидаляє слухачів ... Тож це може бути гарною ідеєю, але я не думаю, що це потрібно для одного запиту ;-)

Сподіваюся, це допоможе вам, Тьєррі


: | це абсурд: | я шукав спосіб зупинити ... але мені було цікаво, і якби це було мене кодування, за деяким сценарієм: я зробив би це так: y = x.subscribe((x)=>data = x); тоді користувач змінив входи і x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()ей, ти використовував наші серверні ресурси, я виграв 'не кину тебе геть ..... та за іншим сценарієм: просто зателефонуй, y.stop()кидай все геть
мертвийManN

16

Відмова від підписки є обов'язковим , якщо ви хочете детерміноване поведінку на всіх швидкостях мережі.

Уявіть, що компонент А відображається на вкладці - Ви натискаєте кнопку, щоб надіслати запит "GET". Щоб відповідь повернулася, потрібно 200 мс. Таким чином, ви можете безпечно закрити вкладку в будь-який момент, знаючи це, машина буде швидшою, ніж ви & http відповідь буде оброблена і завершена до закриття вкладки та знищення компонента A.

Як щодо дуже повільної мережі? Ви натискаєте кнопку, запит "GET" потребує 10 секунд, щоб отримати відповідь, але через 5 секунд очікування ви вирішите закрити вкладку. Це знищить компонент А, який буде зібраний сміттям пізніше. Почекай хвилинку! , ми не скасували підписку - зараз через 5 секунд повертається відповідь і буде виконана логіка знищеного компонента. Це виконання зараз розглядається out-of-contextі може призвести до багатьох речей, включаючи дуже низьку продуктивність.

Отже, найкраща практика - використовувати takeUntil()та скасувати підписку на http-дзвінки, коли компонент знищений.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

interface User {
  id: string;
  name: string;
  age: number;
}

@Component({
  selector: 'app-foobar',
  templateUrl: './foobar.component.html',
  styleUrls: ['./foobar.component.scss'],
})
export class FoobarComponent implements OnInit, OnDestroy {
  private user: User = null;
  private destroy$ = new Subject();

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http
      .get<User>('api/user/id')
      .pipe(takeUntil(this.destroy$))
      .subscribe(user => {
        this.user = user;
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();  // trigger the unsubscribe
    this.destroy$.complete(); // finalize & clean up the subject stream
  }
}

Чи можна використовувати BehaviorSubject()замість цього і зателефонувати тільки complete()в ngOnDestroy()?
Сакшам

Вам все одно потрібно буде скасувати підписку ... чому б BehaviourSubjectбуло по-іншому?
Бен Таліадорос

Не потрібно скасовувати підписку на спостереження HttpClient, оскільки вони є кінцевими спостереженнями, тобто вони завершуються після випромінювання значення.
Ятхарт Варшні

Ви все одно повинні скасувати підписку, припустимо, що є перехоплювач, щоб тостити помилки, але ви вже залишили сторінку, яка викликала помилку. Ви побачите повідомлення про помилку на іншій сторінці .... Також подумайте про сторінку перевірки здоров'я, яка закликає всі мікросервіси ... і так далі
Washington Guedes

12

Також з новим модулем HttpClient залишається та ж поведінка пакети / загальні / http / src / jsonp.ts


Наведений вище код здається з одиничного тесту, маючи на увазі, що з HttpClient вам НЕ потрібно самостійно телефонувати .complete () ( github.com/angular/angular/blob/… )
CleverPatrick

Так, ви маєте рацію, але якщо ви оглянете ( github.com/angular/angular/blob/… ), ви побачите те саме. Щодо головного питання, якщо потрібно чітко скасувати підписку? більшості випадків немає, оскільки це буде вирішуватися самим Angular. Може бути випадок, коли може відбутися тривала відповідь, і ви перейшли на інший маршрут, або, можливо, знищили компонент, і в цьому випадку у вас є можливість спробувати отримати доступ до чогось, що більше не існує, викликаючи виняток.
Рікардо Мартінес

8

Не слід скасовувати підписку на спостереження, які завершуються автоматично (наприклад, Http, дзвінки). Але потрібно скасувати підписку на нескінченні спостереження на кшталт Observable.timer().


6

Через деякий час тестування, читання документації та вихідного коду HttpClient.

HttpClient: https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts

HttpXhrBackend : https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts

HttpClientModule: https://indepth.dev/exploring-the-httpclientmodule-in-angular/

Кутова Університет: https://blog.angular-university.io/angular-http/

Цей конкретний тип спостережуваних даних є потоками з одним значенням: якщо HTTP-запит буде успішним, ці спостережувані дані випромінюють лише одне значення, а потім завершуються

І відповідь на весь випуск "чи мені потрібно" скасувати підписку?

Це залежить. HTTP-виклик Memoryleaks - це не проблема. Проблеми полягають у логіці функцій зворотного дзвінка.

Наприклад: Маршрутизація або Логін.

Якщо ваш дзвінок - це дзвінок для входу, вам не доведеться "скасовувати підписку", але вам потрібно переконатися, що користувач залишає сторінку, чи правильно ви обробляєте відповідь у відсутності користувача.


this.authorisationService
      .authorize(data.username, data.password)
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })

Від дратівливого до небезпечного

Тепер лише уявіть, що мережа проходить повільніше, ніж зазвичай, виклик займає довше 5 секунд, а користувач залишає огляд входу та переходить до "перегляду підтримки".

Компонент може бути не активним, але підписка. У разі відповіді, користувач буде раптово перенаправлений (залежно від вашої реалізації handleResponse ()).

Це не добре.

Також просто уявіть, що користувач залишає ПК, вважаючи, що він ще не ввійшов до системи. Але ви логічно зареєструєте користувача, тепер у вас є проблема безпеки.

Що ви можете робити БЕЗ підписки?

Зробіть виклик залежним від поточного стану перегляду:

  public isActive = false;
  public ngOnInit(): void {
    this.isActive = true;
  }

  public ngOnDestroy(): void {
    this.isActive = false;
  }

Користувач .pipe(takeWhile(value => this.isActive))переконається, що відповідь обробляється лише тоді, коли перегляд активний.


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })

Але як ви можете бути впевнені, що підписка не спричиняє пам'яті пам'яті?

Ви можете увійти, якщо застосовано "teardownLogic".

TeardownLogic передплати буде викликаний, коли підписка порожня або скасована.


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
    }).add(() => {
        // this is the teardown function
        // will be called in the end
      this.messageService.info('Teardown');
    });

Не потрібно скасовувати підписку. Ви повинні знати, чи є проблеми у вашій логіці, які можуть спричинити проблеми у вашій підписці. І дбати про них. У більшості випадків це не буде проблемою, але особливо при критичних завданнях, таких як авторизація, вам слід подбати про несподівану поведінку, змочіть її за допомогою "скасувати підписку" або іншої логіки, як функція трубопроводу або умовного зворотного виклику.

чому б не завжди завжди скасувати підписку?

Уявіть, що ви робите запит на додавання чи публікацію. Сервер отримує повідомлення в будь-якому випадку, просто відповідь потребує певного часу. Скасувавши підписку, не скасуйте публікацію та не поставте. Але коли ви скасуєте підписку, у вас не буде можливості обробити відповідь або повідомити Користувача, наприклад, через Діалог або Тост / Повідомлення тощо.

Що змушує Користувача вважати, що запит put / post не був виконаний.

Так це залежить. Це ваше дизайнерське рішення, як боротися з такими питаннями.


4

Ви обов'язково повинні прочитати цю статтю. Він показує, чому ви завжди повинні скасувати підписку навіть з http .

Якщо після створення запиту, але до отримання відповіді від бек-енду, ви вважаєте компонент непотрібним і знищуєте його, ваша підписка зберігатиме посилання на компонент, створюючи таким чином шанс викликати витоки пам'яті.

Оновлення

Наведене вище твердження здається правдивим, але все одно, коли відповідь повернеться, підписка http все одно знищена


1
Так, і ви фактично побачите червоний "скасувати" на вкладці мережі вашого браузера, щоб ви могли бути впевнені, що він працює.
Simon_Weaver

-2

Спостережувані RxJS в основному пов'язані, і працюємо відповідно, якщо ви його підписали. Коли ми створюємо спостережуване і рух, який ми завершуємо, спостережуване автоматично закривається і скасовується.

Вони працюють так само, як і спостерігачі, але зовсім в іншому порядку. Краща практика відписати їх, коли компонент стає знищеним. Згодом це можна зробити, наприклад. це. $ ManagSubscription.unsubscibe ()

Якщо ми створили спостережуваний на зразок нижче згаданий синтаксис, такий як

** повернути новий спостерігаючий ((спостерігач) => {** // Це робить спостережуваним у холодному стані ** observer.complete () **}) **

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.