Нужно ли отказываться от подписки на наблюдаемые объекты, созданные методами Http?

Вам нужно отказаться от подписки на HTTP-вызовы Angular 2, чтобы предотвратить утечку памяти?

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

person born2net    schedule 27.01.2016    source источник
comment
Возможный дубликат Предотвратить утечку памяти в Angular 2?   -  person Eric Martinez    schedule 27.01.2016
comment
См. stackoverflow.com/questions/34461842/ (а также комментарии)   -  person Eric Martinez    schedule 27.01.2016
comment
Если вы читаете этот вопрос, опасайтесь принятого ответа (который ОП написал сам). Если вы читаете другие ответы, ВСЕ они опровергают, что вам не нужно отказываться от подписки. Тот факт, что он набрал наибольшее количество голосов и принят, не делает его правильным.   -  person Liam    schedule 11.03.2021


Ответы (10)


Так что ответ - нет. Ng2 очистит его сам.

Источник службы Http из внутреннего источника Angular 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, который представляет собой бесконечный поток данных, который может быть испущен, например, как прослушиватель DOM click.

СПАСИБО, @rubyboy за помощь в этом.

person born2net    schedule 27.01.2016
comment
что происходит в тех случаях, когда пользователь покидает страницу до того, как приходит ответ, или если ответ никогда не приходит до того, как пользователь покидает представление? Разве это не вызовет утечку? - person Thibs; 06.07.2018
comment
@ 1984 Ответ: ВСЕГДА НЕ ПОДПИСАТЬСЯ. Этот ответ совершенно неверен именно по причине, упомянутой в этом комментарии. Пользователь, уходящий прочь, - это ссылка на память. Кроме того, если код в этой подписке запускается после того, как пользователь уходит, это может вызвать ошибки / иметь неожиданные побочные эффекты. Я обычно использую takeWhile (() = ›this.componentActive) для всех наблюдаемых объектов и просто устанавливаю this.componentActive = false в ngOnDestroy, чтобы очистить все наблюдаемые в компоненте. - person Lenny; 16.02.2019
comment
Да, но вопрос относится к HTTP-вызовам, и я считаю, что клиентский модуль http обрабатывает отмену подписки на наблюдаемые? - person 1984; 17.02.2019
comment
Показанный здесь пример охватывает частный api с глубоким angular. с любым другим частным API он может измениться при обновлении без предварительного уведомления. Практическое правило: если это официально не задокументировано, не делайте никаких предположений. Например, AsynPipe имеет четкую документацию, в которой он автоматически подписывается / отписывается за вас. В документации HttpClient об этом ничего не говорится. - person YoussefTaghlabi; 05.04.2019
comment
@YoussefTaghlabi, к сведению, официальная документация по angular (https://angular.io/guide/http) упоминает, что: AsyncPipe подписывается (и отписывается) за вас автоматически. Так что это действительно официально задокументировано. - person Hudgi; 11.05.2019
comment
@Lenny Я видел много таких рекомендаций даже для http-запросов. Но я не понимаю, как подписанный Observable из HTTP-запроса может вызвать утечку памяти. Насколько я понимаю, XmlHttpRequest удерживается браузером только во время ожидания ответа сервера. После получения ответа XmlHttpRequest и Observable отсоединились от стека и, следовательно, стали объектами сборки мусора. Так почему мы должны заботиться об утечках памяти? P.S. Я понимаю, что это не имеет ничего общего с выполнением обратного вызова для уничтоженного компонента. - person Eugene Khudoy; 24.10.2019
comment
@Hudgi, то, о чем вы говорите, - это когда вы используете асинхронный канал ... но это когда вы используете наблюдаемое непосредственно в шаблоне html. Например: obsrvbl $ | асинхронный - person Georgi; 27.08.2020
comment
Класс, обрабатывающий XHR, можно найти здесь, если кому-то интересно: https://github.com/angular/angular/blob/master/packages/common/http/src/xsrf.ts - person Christian; 20.07.2021

### О чем вы, народ !!!

Итак, есть две причины отказаться от подписки на все наблюдаемые. Похоже, что никто особо не говорит об очень важной второй причине!

  1. Очистите ресурсы. Как говорили другие, это незначительная проблема для наблюдаемых HTTP. Он просто очистится.
  1. Запретить запуск обработчика subscribe.

Обратите внимание, что с наблюдаемым HTTP-объектом, когда вы отказываетесь от подписки, он отменяет для вас базовый запрос в вашем браузере (вы увидите «Отменено» красным цветом на панели «Сеть»). Это означает, что время не будет потрачено зря на загрузку или анализ ответа. Но на самом деле это отступление от моей основной мысли, представленной ниже.

Актуальность номера 2 будет зависеть от того, что делает ваш обработчик подписки:

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

Рассмотрим несколько случаев:

  1. Форма входа. Вы вводите имя пользователя и пароль и нажимаете «Войти». Что делать, если сервер работает медленно, и вы решите нажать Escape, чтобы закрыть диалоговое окно? Вы, вероятно, предположите, что не вошли в систему, но если HTTP-запрос вернулся после того, как вы нажали escape, тогда вы все равно будете выполнять любую логику, которая у вас есть. Это может привести к перенаправлению на страницу учетной записи, установке нежелательного файла cookie для входа или переменной токена. Вероятно, это не то, чего ожидал ваш пользователь.

  2. Форма для отправки электронного письма.

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

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

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

  1. Уничтоженный / закрытый компонент Angular. Любые наблюдаемые http-объекты, все еще работающие в это время, завершат и запустят свою логику, если вы не откажетесь от подписки в onDestroy(). Будут ли последствия тривиальны или нет, будет зависеть от того, что вы делаете в обработчике подписки. Если вы попытаетесь обновить то, чего больше не существует, вы можете получить сообщение об ошибке.

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

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

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

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

person Simon_Weaver    schedule 14.08.2018
comment
Также обратите внимание, что если у вас есть служба, которая возвращает необработанный http overvable, а затем вы передаете ее перед подпиской, вам нужно только отказаться от подписки на конечную наблюдаемую, а не на базовую наблюдаемую http. Фактически у вас даже не будет подписки на http напрямую, так что вы не сможете. - person Simon_Weaver; 23.08.2018
comment
Несмотря на то, что при отмене подписки запрос браузера отменяется, сервер по-прежнему выполняет этот запрос. Есть ли способ настроить сервер, чтобы тоже прервать операцию? Я отправляю http-запросы в быстрой последовательности, большинство из которых отменяются отказом от подписки. Но сервер по-прежнему обрабатывает запросы, отмененные клиентом, в результате чего законный запрос ожидает. - person bala; 06.11.2019
comment
@bala, вам придется придумать для этого свой собственный механизм, который не будет иметь ничего общего с RxJS. Например, вы можете просто поместить запросы в таблицу и запустить их через 5 секунд в фоновом режиме, а затем, если что-то нужно отменить, вы просто удалите или установите флаг на более старых строках, что остановит их выполнение. Будет полностью зависеть от вашего приложения. Однако, поскольку вы упомянули, что сервер блокирует, он может быть настроен так, чтобы разрешать только один запрос за раз, но это снова будет зависеть от того, что вы использовали. - person Simon_Weaver; 21.01.2020
comment
????????????Совет 2 - это ПРО-СОВЕТ ???????????? для всех, кто хочет отказаться от подписки на несколько подписок, вызвав только одну функцию. Добавьте, используя метод .add (), а затем .unsubscribe () в ngDestroy. - person abhay tripathi; 19.05.2020

Отказ от подписки является ОБЯЗАТЕЛЬНЫМ, если вы хотите детерминированного поведения на всех скоростях сети.

Представьте, что компонент A отображается на вкладке - вы нажимаете кнопку, чтобы отправить запрос GET. Для возврата ответа требуется 200 мс. Таким образом, вы можете безопасно закрыть вкладку в любой момент, зная, что машина будет быстрее, чем вы, и HTTP-ответ будет обработан и завершен до закрытия вкладки и уничтожения компонента A.

Как насчет очень медленной сети? Вы нажимаете кнопку, на запрос "GET" требуется 10 секунд для получения ответа, но через 5 секунд ожидания вы решаете закрыть вкладку. Это приведет к уничтожению компонента A, который позже будет удален сборщиком мусора. Подождите!, мы не отказались от подписки - сейчас через 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
  }
}
person un33k    schedule 30.07.2019
comment
Можно ли вместо этого использовать BehaviorSubject() и вызывать только complete() в ngOnDestroy()? - person Saksham; 09.06.2020
comment
Вам все равно придется отказаться от подписки ... Почему BehaviourSubject должно быть иначе? - person Ben Taliadoros; 12.06.2020
comment
Нет необходимости отказываться от подписки на наблюдаемые HttpClient, поскольку они являются конечными наблюдаемыми, то есть они завершаются после выдачи значения. - person Yatharth Varshney; 12.06.2020
comment
Вы все равно должны отказаться от подписки, предположим, что есть перехватчик для всплывающих сообщений об ошибках, но вы уже покинули страницу, которая вызвала ошибку ... вы увидите сообщение об ошибке на другой странице ... Также подумайте о странице проверки работоспособности, которая вызывает все микросервисы ... и так далее - person Washington Guedes; 19.06.2020
comment
Где в приведенном примере поведение отказа от подписки? - person Joseph Gabriel; 12.07.2020
comment
@Saksham Пожалуйста, используйте Subject (), поскольку мы заботимся только о будущем излучении из-за вызова next() в нашем ngOnDestroy (). BehaviorSubject () сразу же выдает текущее значение, вызывая отмену подписки (). - person un33k; 19.07.2020
comment
@YatharthVarshney Я рекомендую вам еще раз прочитать описанный мной сценарий, прежде чем принимать какие-либо окончательные решения. Детерминированное программное обеспечение - это результат детерминированных путей выполнения. - person un33k; 19.07.2020
comment
@JosephGabriel, unsubscribe происходит через takeUntil(this.destroy$), и это запускается из ngOnDestory путем вызова this.destroy$.next();, который выполняется автоматически при уничтожении компонента или службы. - person un33k; 19.07.2020
comment
попробуйте также асинхронный канал: D, это рекомендуемый способ для автоматической подписки и отмены подписки - person Rebai Ahmed; 14.07.2021

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

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

Тем не менее, unsubscribe удаляет слушателей ... Так что это может быть хорошей идеей, но я не думаю, что это необходимо для одного запроса ;-)

Надеюсь, это поможет тебе, Тьерри

person Thierry Templier    schedule 27.01.2016
comment
: | это абсурд: | я искал способ остановиться ... но мне было интересно, и если бы я кодировал, в каком-то сценарии: я бы сделал это так: y = x.subscribe((x)=>data = x); затем пользователь изменил входные данные и x.subscribe((x)=>cachlist[n] = x); y.unsubscribe() эй, вы использовали ресурсы нашего сервера, я не выброшу тебя ..... и в другом сценарии: просто позвони y.stop() выбрось все - person deadManN; 13.07.2019

Через некоторое время тестирования, чтения документации и исходного кода 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/

Angular Univeristy: https://blog.angular-university.io/angular-http/

Этот конкретный тип Observables представляет собой потоки с одним значением: если HTTP-запрос успешен, эти наблюдаемые объекты будут выдавать только одно значение, а затем завершать

И ответ на весь вопрос "НУЖНО ли мне отказаться от подписки?"

Это зависит. HTTP-вызов утечки памяти не является проблемой. Проблема заключается в логике ваших функций обратного вызова.

Например: маршрутизация или вход.

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


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');
    });

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

почему бы просто не отказаться от подписки?

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

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

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

person Luxusproblem    schedule 01.03.2020

Также с новым модулем HttpClient остается то же поведение  пакеты / common / http / src / jsonp.ts

person Ricardo Martínez    schedule 20.02.2018
comment
Похоже, что приведенный выше код взят из модульного теста, подразумевающего, что с HttpClient вам НЕОБХОДИМО вызвать .complete () самостоятельно (github.com/angular/angular/blob/) - person CleverPatrick; 12.11.2018
comment
Да, вы правы, но если вы проверите (github.com/angular/angular/blob/) вы увидите то же самое. По основному вопросу, нужно ли явно отказаться от подписки? в большинстве случаев нет, поскольку этим занимается сам Angular. Это может быть случай, когда может произойти длинный ответ, и вы перешли на другой маршрут или, возможно, уничтожили компонент, и в этом случае у вас есть возможность попытаться получить доступ к чему-то, чего больше не существует, и возникнет исключение. - person Ricardo Martínez; 16.03.2019

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

person Mykhailo Mykhaliuk    schedule 04.07.2018

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

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

Обновить

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

person MTZ    schedule 13.03.2019
comment
Да, и вы действительно увидите красную кнопку «Отмена» на вкладке сети вашего браузера, чтобы вы могли быть уверены, что это работает. - person Simon_Weaver; 01.07.2019
comment
Ссылка не работает. - person robert; 22.06.2021
comment
@robert Да, похоже, сейчас сломано. - person MTZ; 23.06.2021

Хорошая часть, которая может помочь понять это, заключается в том, что HTTP-запрос не выполняется, если нет вызова функции подписки. Хотя ответы на этой странице, кажется, предлагают две разные практики, на самом деле это не имеет большого значения, поскольку требуемое поведение будет контролироваться асинхронным конвейером, как указано в angular docs (хотя он упоминается намного позже в разделе« Создание запроса DELETE »):

AsyncPipe подписывается (и отписывается) за вас автоматически.

На самом деле, еще труднее найти в документации какой-либо пример, где такие наблюдаемые явно отменяются с помощью вызова функции отмены подписки.

person Naman Bakhru    schedule 25.03.2021

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

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

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

** return new Observable ((Observable) => {** // Делает наблюдаемым в холодном состоянии ** Observable.complete () **}) **

person Sachin Mishra    schedule 12.08.2018