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

Вам нужно отписаться от 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());
            ...

12 ответов

Так что ответ - нет. 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вопреки infiniteObservableкоторый представляет собой бесконечный поток данных может быть передан как DOM click слушатель например.

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

О чем вы, люди, говорите!!!

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

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

2) Предотвратить subscribe обработчик от запуска.

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

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

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

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

2) Форма для отправки по электронной почте. Когда HTTP-сообщение находится в полете, важно понимать, что unsubscribe() НЕ отменяет HTTP-запрос. Он уже в пути - и это не имеет ничего общего с Angular или RxJS - просто так работает интернет. Он попадет на ваш сервер, и письмо будет отправлено. (Вам нужно реализовать какой-то механизм отложенной отправки, как в случае кнопки отмены Gmail, если вам нужно более изощренное поведение.)

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

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

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

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

Отмена подписки ОБЯЗАНА, если вы хотите детерминистическое поведение на всех скоростях сети.

Представьте, что компонент 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
  }
}

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

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

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

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

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

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

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

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

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

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

Также с новым модулем HttpClient, остается то же самое поведение пакеты / общие / HTTP / SRC / jsonp.ts

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

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

Обновить

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

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

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

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

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

Да, необходимо отписаться.

Из-за документации Angular :

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

Я создал целое огромное корпоративное приложение, полагая, что httpclient автоматически отписывается, и я не сталкивался ни с какими утечками памяти.

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

Нам не нужно явно вызывать метод отписки. Вы можете проверить состояние наблюдаемого с помощью закрытого метода.

Другие вопросы по тегам