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/…(およびコメントも)を参照してください
Eric Martinez

回答:


253

したがって、答えは「いいえ」です。Ng2それ自体をクリーンアップします。

AngularのHttp XHRバックエンドソースからのHttpサービスソース:

ここに画像の説明を入力してください

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 ObservableDOM clickリスナーのように、無限のデータストリームを出力できます。

この件について、@ ruby​​boyに感謝します。


17
ユーザーが応答を受け取る前にページを離れた場合、またはユーザーがビューを離れる前に応答がなかった場合はどうなりますか?それは漏れを引き起こさないでしょうか?
Thibs

1
以上も知りたいです。
1984年

4
@ 1984答えは常にUNSUBSCRIBEです。この回答は、このコメントで取り上げた理由のために完全に間違っています。ナビゲートするユーザーはメモリリンクです。さらに、ユーザーが離れた後にそのサブスクライブのコードを実行すると、エラーが発生したり、予期しない副作用が発生したりする可能性があります。私はすべてのオブザーバブルでtakeWhile(()=> this.componentActive)を使用する傾向があり、ngOnDestroyでthis.componentActive = falseを設定してコンポーネントのすべてのオブザーバブルをクリーンアップします。
レニー

4
ここに表示される例は、ディープアングルプライベートAPIをカバーしています。他のプライベートAPIでは、予告なしにアップグレードで変更される可能性があります。原則として、正式に文書化されていない場合は、想定しないでください。たとえば、AsynPipeには、自動的にサブスクライブ/サブスクライブ解除するという明確なドキュメントがあります。HttpClientドキュメントはそれについて何も言及していません。
YoussefTaghlabi

1
@ YoussefTaghlabi、FYI、公式Angularドキュメント(angular.io/guide/http)には、「AsyncPipeが自動的にサブスクライブ(およびサブスクライブ解除)する」と記載されています。それで、それは確かに公式に文書化されています。
Hudgi、

96

何の話をしているの!

わかりましたので、オブザーバブルの登録を解除する理由は2つあります。誰もが非常に重要な2番目の理由についてあまり話していないようです!

1)リソースをクリーンアップします。他の人が言ったように、これはHTTPオブザーバブルでは無視できる問題です。クリーンアップするだけです。

2)subscribeハンドラーが実行されないようにします。

(HTTPの場合、これは実際にはブラウザーでの要求もキャンセルします。そのため、応答の読み取りに時間を浪費することはありません。しかし、それは実際には、以下の私の主なポイントの余地です。)

番号2の関連性は、サブスクライブハンドラーの処理によって異なります。

あなたの場合はsubscribe()、ハンドラ関数が呼び出されます何でも、それが閉じている場合は望ましくないか、配置されている副作用のいずれかの種類を持っている、あなたは、解除(または条件付きロジックを追加する)、実行されるのを防ぐためにしなければなりません。

いくつかのケースを考えてみましょう:

1)ログインフォーム。ユーザー名とパスワードを入力して、「ログイン」をクリックします。サーバーが遅く、エスケープを押してダイアログを閉じることにした場合はどうなりますか?ログインしていないと思われるかもしれませんが、エスケープを押した後にhttpリクエストが返された場合は、そこにあるロジックを実行します。これにより、アカウントページにリダイレクトされ、不要なログインCookieまたはトークン変数が設定される可能性があります。これはおそらくユーザーが期待したものではありません。

2)「メール送信」フォーム。

場合subscribe「のsendEmail」のハンドラがトリガAのようなものアニメーション「あなたのメールが送信された」ん、あなたは例外または不要な動作を得ることが破棄されているアクセス何にも別のページまたは試行にあなたを転送します。

また、unsubscribe()「キャンセル」を意味すると想定しないように注意してください。HTTPメッセージが処理中にunsubscribe()なると、HTTPリクエストがすでにサーバーに到達している場合、HTTPリクエストはキャンセルされません。それはあなたに戻ってくる応答をキャンセルするだけです。そして、電子メールはおそらく送信されます。

サブスクリプションを作成して、UIコンポーネント内で直接電子メールを送信する場合は、おそらく破棄時にサブスクライブを解除する必要がありますが、電子メールが非UI集中型サービスによって送信されている場合は、おそらく必要はありません。

3)破壊/クローズされたAngularコンポーネント。その時点でまだ実行されているhttpオブザーバブルは、で登録解除しない限り、ロジックを完了して実行しますonDestroy()。結果が取るに足らないものかどうかは、サブスクライブハンドラーで何をするかによって異なります。存在しないものを更新しようとすると、エラーが発生する可能性があります。

コンポーネントが破棄された場合に必要なアクションと不要なアクションが存在する場合があります。たとえば、送信メールに「シューッという音」が聞こえるかもしれません。コンポーネントが閉じている場合でもこれを再生したいと思うかもしれませんが、コンポーネントでアニメーションを実行しようとすると失敗します。その場合、subscribe内のいくつかの追加の条件付きロジックが解決策になります-そして、httpオブザーバブルの登録を解除したくないでしょう。

したがって、実際の質問に答えて、メモリリークを回避するためにそれを行う必要はありません。ただし、例外をスローしたり、アプリケーションの状態を破壊したりする可能性のあるコードを実行することで不要な副作用が引き起こされるのを防ぐために、(多くの場合)これを行う必要があります。

ヒント:にSubscriptionは、closed高度なケースで役立つブールプロパティが含まれています。HTTPの場合、これは完了時に設定されます。Angularでは、ハンドラーがチェックできる_isDestroyedプロパティを設定すると便利な場合がありngDestroyますsubscribe

ヒント2:複数のサブスクリプションを処理する場合は、アドホックnew Subscription()オブジェクトとadd(...)その他のサブスクリプションを作成できます。メインのサブスクリプションを解除すると、追加されたすべてのサブスクリプションも解除されます。


2
また、生のhttp上書き可能を返すサービスがあり、サブスクライブする前にそれをパイプ処理する場合、基になるhttp監視可能ではなく、最後のオブザーバブルの登録を解除するだけでよいことにも注意してください。実際、httpへのサブスクリプションさえ直接持っていないので、できません。
Simon_Weaver

登録を解除するとブラウザのリクエストがキャンセルされますが、サーバーはリクエストに応じて動作します。サーバーを親密にして操作も中止する方法はありますか?私はhttpリクエストを連続して送信していますが、そのほとんどは登録解除によってキャンセルされます。ただし、サーバーは引き続きクライアントによってキャンセルされた要求を処理し、正当な要求を待機させます。
バラ

@balaこれを行うには独自のメカニズムを考え出す必要があります-これはRxJSとは何の関係もありません。たとえば、リクエストをテーブルに入れて5秒後にバックグラウンドで実行し、何かをキャンセルする必要がある場合は、古い行を削除するか、実行を停止するフラグを設定します。アプリケーションが何であるかに完全に依存します。ただし、サーバーがブロックしていると述べたので、一度に1つの要求のみを許可するように構成されている可能性がありますが、これも使用しているものによって異なります。
Simon_Weaver

🔥🔥🔥Tip2-PRO-TIPです。1つの関数を呼び出すだけで複数のサブスクリプションを解除したい人のためのヒントです。.add()メソッドを使用して追加し、ngDestroyで.unsubscribe()を追加します。
abhay tripathi

23

呼び出すunsubscribeメソッドは、このメソッドは呼び出しますので、進行中のHTTPリクエストをキャンセルすることはなく、あるabort負荷およびエラーイベントの基本となるXHRオブジェクトと削除リスナーに1つずつ:

// 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

退会はありMUSTしたい場合は、確定すべてのネットワーク速度での動作を。

コンポーネント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
  }
}

BehaviorSubject()代わりに使用して、すぐcomplete()に呼び出すことはできngOnDestroy()ますか?
Saksham

あなたはまだ退会する必要があります...なぜBehaviourSubject違うのでしょうか?
Ben Taliadoros

HttpClientオブザーバブルは有限のオブザーバブルであるため、サブスクライブを解除する必要はありません。つまり、値を発行した後に完了します。
yatharth varshney

とにかく、サブスクライブを解除する必要があります。トーストエラーのインターセプターがあるが、エラーをトリガーしたページを既に離れている場合は、別のページにエラーメッセージが表示されます。...ヘルスチェックページについても考えてください。すべてのマイクロサービスを呼び出す...など
ワシントンゲデス

12

また、新しいHttpClientモジュールを使用しても、動作は変わりません packages / common / http / src / jsonp.ts


上記のコードは、独自の(上)のHttpClientで、あなたは(.completeを呼び出す必要がないことを示唆し、ユニットテストからのようですgithub.com/angular/angular/blob/...
CleverPatrick

はい、あなたは正しいですが、調べると(github.com/angular/angular/blob/…)、同じことが表示されます。メインの質問について、明示的に退会する必要がある場合はどうなりますか?Angular自体で処理されるため、ほとんどの場合はありません。長い応答が発生し、別のルートに移動したり、コンポーネントを破壊したりする可能性がある場合、存在しないものにアクセスしようとすると例外が発生する可能性があります。
RicardoMartínez19年

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

HttpClientModulehttps : //indepth.dev/exploring-the-httpclientmodule-in-angular/

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

この特定のタイプのオブザーバブルは単一値ストリームです。HTTPリクエストが成功した場合、これらのオブザーバブルは1つの値のみを発行してから完了します。

そして、解約するには「do i NEED」の問題全体に対する答えは?

場合によります。 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()の実装によって異なります)。

これは良くない

また、ユーザーがまだPCを離れており、まだログインしていないと考えているとします。しかし、あなたはロジックでユーザーをログインさせます、今、あなたはセキュリティ問題を抱えています。

登録解除せずに何ができますか?

ビューの現在の状態に応じて呼び出しを行います。

  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リクエストを行うと想像してください。サーバーはどちらの方法でもメッセージを受信しますが、応答だけにしばらく時間がかかります。登録を解除すると、投稿の取り消しや投稿は行われません。ただし、退会すると、たとえばダイアログやトースト/メッセージなどを介して、応答を処理したり、ユーザーに通知したりすることができなくなります。

Wichは、ユーザーにput / postリクエストが実行されなかったと信じ込ませます。

したがって、状況によって異なります。そのような問題にどのように対処するかは、あなたの設計上の決定です。


4

あなたは間違いなくこの記事を読むべきです。これは、なぜhttpからでも退会する必要があるのか​​を示しています。

リクエストの作成後、バックエンドから回答を受け取る前に、コンポーネントが不要であると見なして破棄した場合、サブスクリプションはコンポーネントへの参照を維持し、メモリリークを引き起こす可能性があります。

更新

上記の確認は真実のようですが、とにかく、答えが戻ってきたときにhttpサブスクリプションがとにかく破棄されます


1
はい、実際にブラウザのネットワークタブに赤い「キャンセル」が表示されるので、確実に機能します。
Simon_Weaver

-2

RxJSオブザーバブルは基本的に関連付けられており、サブスクライブするとそれに応じて機能します。オブザーバブルを作成してそれを完了すると、オブザーバブルは自動的に閉じられ、サブスクライブ解除されます。

ウォッチャーと同じように動作しますが、順序はかなり異なります。コンポーネントが破棄されているときにサブスクライブを解除することをお勧めします。その後、たとえば、this。$ manageSubscription.unsubscibe()

以下のようにオブザーバブルを作成した場合、次のような構文

** return new Observable((observer)=> {** //コールドステートで観測可能にする** observer.complete()**}) **

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.