web-dev-qa-db-ja.com

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());
            ...
140
born2net

だから答えはいいえ、あなたはしません。 Ng2はそれ自体をクリーンアップします。

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

enter image description here

結果を取得した後、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)で発生するため、Observableのステータスがすべて完了して完了したときに確認できます。

そして結果

enter image description here

したがって、Ng2 autoがサブスクライブを解除してもリークは発生しません!

素敵な言及:このObservableは、finiteに分類されますが、infiniteとは異なり、Observableは、DOM clickリスナーのようにデータの無限ストリームを発行できます。

おかげで、これについての助けを@rubyboy。

197
born2net

あなたは何について話しているのですか!

わかりましたので、観察可能なものからサブスクライブを解除する2つの理由があります。誰も2番目のことについてあまり話していないようです!

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

2)subscribeハンドラーが実行されないようにします。 (HTTPの場合、これは実際にブラウザでリクエストをキャンセルします。そのため、レスポンスを読むのに時間を浪費することはありません。しかし、実際には以下の主なポイントは別です。)

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

subscribe()ハンドラー関数に何らかの呼び出しが閉じられたり破棄されたりする場合に望ましくない副作用がある場合、実行を防ぐためにサブスクライブを解除(または条件ロジックを追加)する必要があります。

いくつかのケースを考慮してください:

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

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

'sendEmail'のsubscribeハンドラーが 'Your email is sent'アニメーションをトリガーするようなことをした場合、別のページに移動するか、破棄されたものにアクセスしようとすると、例外または不要な動作が発生する可能性があります。

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

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

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

場合によっては、コンポーネントが破棄される場合に必要なアクションとそうでないアクションがあります。たとえば、送信された電子メールに対して「シューッという音」が聞こえる場合があります。コンポーネントが閉じられている場合でも、おそらくこれを再生する必要がありますが、コンポーネントでアニメーションを実行しようとすると失敗します。その場合、サブスクライブ内の追加の条件付きロジックが解決策になります。httpobservableのサブスクライブを解除したくないでしょう。

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

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

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

40
Simon_Weaver

unsubscribeメソッドを呼び出すことは、進行中のHTTPリクエストをキャンセルすることです。このメソッドは、基礎となるXHRオブジェクトでabortを呼び出し、ロードイベントおよびエラーイベントでリスナーを削除するためです。

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

とは言っても、unsubscribeはリスナーを削除します...それは良いアイデアかもしれませんが、単一の要求に必要だとは思わない;-)

お役に立てばと思います、ティエリー

19

また、新しいHttpClientモジュールでは、同じ動作を維持します packages/common/http/src/jsonp.ts

11

自動的に完了するオブザーバブル(Http、呼び出しなど)の購読を解除しないでください。ただし、Observable.timer()のような無限のオブザーバブルのサブスクリプションを解除する必要があります。

8
Misha Mykhalyuk

this の記事を必ずお読みください。 httpからも常にサブスクライブを解除するの理由を示しています。

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

更新

上記の肯定は本当のようですが、とにかく、答えが戻ってくると、HTTPサブスクリプションは破棄されます

2
MTZ

登録解除MUSTすべてのネットワーク速度で決定論的動作が必要な場合です。

ImagineコンポーネントAがタブにレンダリングされる-ボタンをクリックして「GET」リクエストを送信します。応答が戻るには200ミリ秒かかります。そのため、タブは閉じられ、コンポーネントAが破棄される前に、マシンがあなたより速くなり、http応答が処理されて完了することを知っていれば、いつでもタブを閉じることができます。

非常に遅いネットワークではどうですか?ボタンをクリックすると、 '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
  }
}
0
un33k