Нужно ли отписываться от наблюдаемых, созданных методами 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());
            ...
born2net
источник
1
Возможный дубликат предотвращения утечек памяти в Angular 2?
Эрик Мартинес
1
См. Stackoverflow.com/questions/34461842/… (и комментарии также)
Эрик Мартинес

Ответы:

253

Так что ответ - нет. Ng2уберусь сам.

Источник службы Http из внутреннего источника Angular's Http XHR:

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

Обратите внимание, как он работает 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 за помощь в этом.

born2net
источник
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(). Будут ли последствия тривиальными или нет, будет зависеть от того, что вы делаете в обработчике подписки. Если вы попытаетесь обновить то, что больше не существует, вы можете получить ошибку.

Иногда у вас могут быть какие-то действия, которые вы захотите, если компонент удаляется, а некоторые - нет. Например, может быть, у вас есть звук «swoosh» для отправленного электронного письма. Возможно, вы захотите, чтобы это воспроизводилось, даже если компонент был закрыт, но если вы попытаетесь запустить анимацию на компоненте, это не получится. В этом случае некоторая дополнительная условная логика внутри подписки будет решением - и вы НЕ захотите отписываться от наблюдаемой http.

Поэтому, отвечая на реальный вопрос, нет необходимости делать это, чтобы избежать утечек памяти. Но вы должны делать это (часто), чтобы избежать нежелательных побочных эффектов, вызванных выполнением кода, который может вызвать исключения или испортить состояние вашего приложения.

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

Совет 2. Если вы обрабатываете несколько подписок, вы можете создать специальный new Subscription()объект и add(...)любые другие подписки на него - поэтому, когда вы отмените подписку на основную, она также отменит все добавленные подписки.

Simon_Weaver
источник
2
Также обратите внимание, что если у вас есть служба, которая возвращает необработанный необработанный http, и вы передаете его по конвейеру перед подпиской, то вам нужно только отписаться от окончательной наблюдаемой, а не от базовой наблюдаемой http. На самом деле у вас даже не будет подписки на http напрямую, поэтому вы не сможете.
Simon_Weaver
Даже если отмена подписки отменяет запрос браузера, сервер все равно действует на запрос. Есть ли способ интимного сервера, чтобы прервать операцию тоже? Я отправляю http-запросы в быстрой последовательности, большинство из которых отменяются путем отписки. Но сервер по-прежнему работает с запросами, отмененными клиентом, что заставляет законный запрос ждать.
бала
@bala, для этого тебе придется придумать свой собственный механизм - который не будет иметь ничего общего с RxJS. Например, вы можете просто поместить запросы в таблицу и запустить их через 5 секунд в фоновом режиме, а затем, если нужно что-то отменить, вы просто удалите или установите флажок в старых строках, который остановит их выполнение. Будет полностью зависеть от того, что ваше приложение. Однако, поскольку вы упоминаете, что сервер блокирует, он может быть настроен на разрешение только одного запроса за раз - но это опять-таки будет зависеть от того, что вы используете.
Simon_Weaver
IpTip 2 - это PRO-TIP, 🔥🔥🔥 для всех, кто хочет отписаться от нескольких подписок, вызвав только одну функцию. Добавьте с помощью метода .add (), а затем .unsubscribe () в ngDestroy.
Абхай Трипати
23

Вызов unsubscribeметода скорее отменяет выполняющийся HTTP-запрос, так как этот метод вызывает тот abortиз базового объекта XHR и удаляет прослушиватели событий load и error:

// 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()выбрасывай все прочь
deadManN
16

Отказ от подписки является обязательным , если вы хотите детерминированное поведение на всех скоростях сети.

Представьте, что компонент A отображается на вкладке. Вы нажимаете кнопку, чтобы отправить запрос «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
  }
}
un33k
источник
Можно ли использовать BehaviorSubject()вместо этого и позвонить только complete()в ngOnDestroy()?
Сакшам
Тебе все равно нужно отписаться ... почему бы BehaviourSubjectиначе?
Бен Талиадорос
Нет необходимости отписываться от наблюдаемых HttpClient, поскольку они являются конечными наблюдаемыми, т.е. они завершаются после выдачи значения.
Яхарт Варшней
В любом случае вам следует отписаться, предположим, что имеется перехватчик для сообщения об ошибках, но вы уже покинули страницу, которая вызвала ошибку ... вы увидите сообщение об ошибке на другой странице .... Также подумайте о странице проверки работоспособности, которая вызывает все микросервисы ... и так далее
Вашингтон Гуэдес
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');
    });

Вам не нужно отписываться. Вы должны знать, есть ли проблемы в вашей логике, которые могут вызвать проблемы в вашей подписке. И позаботься о них. В большинстве случаев это не будет проблемой, но особенно в критических задачах, таких как авторизация, вы должны позаботиться о непредвиденном поведении, помешать его «отписаться» или другой логике, такой как конвейер или функции условного обратного вызова.

почему просто не всегда отписаться?

Представьте, что вы делаете запрос на размещение или публикацию. Сервер получает сообщение в любом случае, только ответ занимает некоторое время. Отписаться, не отменить сообщение или положить. Но когда вы отмените подписку, у вас не будет возможности обработать ответ или проинформировать пользователя, например, через диалог, тост / сообщение и т. Д.

Это заставляет Пользователя полагать, что запрос на размещение / публикацию не был выполнен.

Так что это зависит. Это ваше дизайнерское решение, как бороться с такими проблемами.

Luxusproblem
источник
4

Вы обязательно должны прочитать эту статью. Он показывает, почему вы всегда должны отписаться даже от http .

Если после создания запроса, но до получения ответа от серверной части, вы посчитаете компонент ненужным и уничтожите его, ваша подписка сохранит ссылку на компонент, создавая тем самым шанс вызвать утечки памяти.

Обновить

Приведенное выше утверждение кажется верным, но в любом случае, когда ответ возвращается, подписка http все равно уничтожается

MTZ
источник
1
Да, и на самом деле вы увидите красный «Отмена» на вкладке сети вашего браузера, так что вы можете быть уверены, что он работает.
Simon_Weaver
-2

Наблюдаемые RxJS в основном связаны и работают соответственно подписке. Когда мы создаем наблюдаемое, а движение мы его завершаем, наблюдаемое автоматически закрывается и отписывается.

Они работают так же, как наблюдатели, но в совершенно другом порядке. Лучшая практика - отписаться от них, когда компонент становится уничтоженным. Впоследствии мы можем сделать это, например ,. это. $ manageSubscription.unsubscibe ()

Если мы создали наблюдаемый, как приведенный ниже синтаксис, такой как

** возвращаем новое Наблюдаемое ((наблюдатель) => {** // Делает наблюдаемое в холодном состоянии ** Наблюдатель.complete () **}) **

Сачин Мишра
источник