Có cần phải hủy đăng ký khỏi các đài quan sát được tạo bởi các phương thức http không?


211

Bạn có cần hủy đăng ký các cuộc gọi http của Angular 2 để tránh rò rỉ bộ nhớ không?

 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
Xem stackoverflow.com/questions 432461842 / (cũng như các bình luận)
Eric Martinez

Câu trả lời:


253

Vì vậy, câu trả lời là không, bạn không. Ng2sẽ tự làm sạch nó

Nguồn dịch vụ http, từ nguồn phụ trợ http XHR của Angular:

nhập mô tả hình ảnh ở đây

Chú ý cách nó chạy complete()sau khi nhận được kết quả. Điều này có nghĩa là nó thực sự hủy đăng ký khi hoàn thành. Vì vậy, bạn không cần phải tự làm điều đó.

Đây là một bài kiểm tra để xác nhận:

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

Như mong đợi, bạn có thể thấy rằng nó luôn được hủy đăng ký tự động sau khi nhận được kết quả và kết thúc với các toán tử quan sát được. Điều này xảy ra vào thời gian chờ (# 3) để chúng tôi có thể kiểm tra trạng thái có thể quan sát được khi tất cả đã hoàn tất và hoàn tất.

Và kết quả

nhập mô tả hình ảnh ở đây

Vì vậy, không có rò rỉ sẽ tồn tại như Ng2hủy đăng ký tự động!

Rất vui được đề cập: Điều này Observableđược phân loại là finite, ngược lại với infinite Observableluồng dữ liệu vô hạn có thể được phát ra như trình clicknghe DOM chẳng hạn.

THANKS, @rubyboy đã giúp đỡ về điều này.


17
Điều gì xảy ra trong trường hợp người dùng rời khỏi trang trước khi phản hồi đến hoặc nếu phản hồi không bao giờ xuất hiện trước khi người dùng rời khỏi chế độ xem? Điều đó sẽ không gây rò rỉ?
Thibs

1
Tôi cũng muốn biết ở trên.
1984

4
@ 1984 Câu trả lời là LUÔN LUÔN UNSUBSCRIBE. Câu trả lời này là hoàn toàn sai cho chính xác lý do đưa ra trong bình luận này. Người dùng điều hướng đi là một liên kết bộ nhớ. Ngoài ra, nếu mã trong đăng ký đó chạy sau khi người dùng điều hướng đi, nó có thể gây ra lỗi / có tác dụng phụ không mong muốn. Tôi có xu hướng sử dụng TakeWhile (() => this.componentActive) trên tất cả các vật quan sát và chỉ đặt this.componentActive = false trong ngOnDestroy để dọn sạch tất cả các vật quan sát trong một thành phần.
Lenny

4
Ví dụ hiển thị ở đây bao gồm một api tư nhân góc sâu. với bất kỳ api riêng nào khác, nó có thể thay đổi khi nâng cấp mà không cần thông báo trước. Nguyên tắc chung là: Nếu nó không được ghi nhận chính thức, đừng đưa ra bất kỳ giả định nào. Ví dụ, AsynPipe có tài liệu rõ ràng rằng nó tự động đăng ký / hủy đăng ký cho bạn. Các tài liệu httpClient không đề cập bất cứ điều gì về điều đó.
YoussefTaghlabi

1
@YoussefTaghlabi, FYI, tài liệu góc chính thức ( angular.io/guide/http ) có đề cập rằng: "AsyncPipe đăng ký (và hủy đăng ký) cho bạn tự động." Vì vậy, nó thực sự chính thức được ghi nhận.
Hudgi

96

Bạn đang nói về cái gì vậy !!!

OK, vì vậy có hai lý do để hủy đăng ký khỏi bất kỳ quan sát nào. Dường như không ai nói nhiều về lý do thứ hai rất quan trọng!

1) Dọn dẹp tài nguyên. Như những người khác đã nói đây là một vấn đề không đáng kể đối với các thiết bị quan sát HTTP. Nó sẽ tự làm sạch thôi.

2) Ngăn không cho người subscribexử lý chạy.

(Đối với HTTP, điều này thực sự cũng sẽ hủy yêu cầu trong trình duyệt - vì vậy sẽ không lãng phí thời gian để đọc phản hồi. Nhưng đó thực sự là một vấn đề chính của tôi bên dưới.)

Sự liên quan của số 2 sẽ phụ thuộc vào những gì trình xử lý đăng ký của bạn làm:

Nếu subscribe()chức năng xử lý của bạn có bất kỳ loại tác dụng phụ nào không mong muốn nếu bất kỳ cuộc gọi nào bị đóng hoặc xử lý thì bạn phải hủy đăng ký (hoặc thêm logic điều kiện) để ngăn chặn nó được thực thi.

Hãy xem xét một vài trường hợp:

1) Một hình thức đăng nhập. Bạn nhập tên người dùng và mật khẩu và nhấp vào 'Đăng nhập'. Điều gì xảy ra nếu máy chủ chậm và bạn quyết định nhấn Escape để đóng hộp thoại? Bạn có thể cho rằng bạn chưa đăng nhập, nhưng nếu yêu cầu http được trả về sau khi bạn thoát, thì bạn vẫn sẽ thực hiện bất kỳ logic nào bạn có ở đó. Điều này có thể dẫn đến việc chuyển hướng đến một trang tài khoản, một cookie đăng nhập hoặc biến mã thông báo không mong muốn đang được đặt. Đây có lẽ không phải là những gì người dùng của bạn mong đợi.

2) Mẫu 'gửi email'.

Nếu subscribetrình xử lý cho 'sendEmail' thực hiện một cái gì đó như kích hoạt hoạt hình 'Email của bạn được gửi', hãy chuyển bạn đến một trang khác hoặc cố gắng truy cập bất cứ thứ gì đã bị loại bỏ, bạn có thể có ngoại lệ hoặc hành vi không mong muốn.

Ngoài ra, hãy cẩn thận đừng cho rằng unsubscribe()có nghĩa là 'hủy bỏ'. Khi tin nhắn HTTP đang trong chuyến bay unsubscribe()sẽ KHÔNG hủy yêu cầu HTTP nếu nó đã đến máy chủ của bạn. Nó sẽ chỉ hủy phản hồi trở lại với bạn. Và email có thể sẽ được gửi.

Nếu bạn tạo đăng ký để gửi email trực tiếp bên trong một thành phần UI thì có lẽ bạn sẽ muốn hủy đăng ký, nhưng nếu email được gửi bởi một dịch vụ tập trung không phải UI thì có lẽ bạn sẽ không cần.

3) Một thành phần Angular bị phá hủy / đóng. Bất kỳ thiết bị quan sát http nào vẫn đang chạy tại thời điểm đó sẽ hoàn thành và chạy logic của chúng trừ khi bạn hủy đăng ký onDestroy(). Việc hậu quả có tầm thường hay không sẽ phụ thuộc vào những gì bạn làm trong trình xử lý đăng ký. Nếu bạn cố cập nhật một cái gì đó không còn tồn tại nữa, bạn có thể gặp lỗi.

Đôi khi bạn có thể có một số hành động bạn muốn nếu thành phần bị loại bỏ, và một số bạn sẽ không. Ví dụ: có thể bạn có âm thanh 'swoosh' cho một email đã gửi. Bạn có thể muốn chơi trò này ngay cả khi thành phần đã bị đóng, nhưng nếu bạn cố chạy một hình động trên thành phần thì nó sẽ thất bại. Trong trường hợp đó, một số logic có điều kiện bổ sung bên trong đăng ký sẽ là giải pháp - và bạn sẽ KHÔNG muốn hủy đăng ký http có thể quan sát được.

Vì vậy, để trả lời cho câu hỏi thực tế, không bạn không cần phải làm điều đó để tránh rò rỉ bộ nhớ. Nhưng bạn cần phải làm điều đó (thường xuyên) để tránh các tác dụng phụ không mong muốn được kích hoạt bằng cách chạy mã có thể ném ngoại lệ hoặc làm hỏng trạng thái ứng dụng của bạn.

Mẹo: SubscriptionChứa thuộc tính closedboolean có thể hữu ích trong các trường hợp nâng cao. Đối với HTTP, điều này sẽ được đặt khi hoàn thành. Trong Angular, có thể hữu ích trong một số trường hợp để đặt _isDestroyedthuộc tính ngDestroysubscribetrình xử lý của bạn có thể kiểm tra .

Mẹo 2: Nếu xử lý nhiều đăng ký, bạn có thể tạo một new Subscription()đối tượng đặc biệt và add(...)bất kỳ đăng ký nào khác cho nó - vì vậy khi bạn hủy đăng ký từ chính, nó cũng sẽ hủy đăng ký tất cả các đăng ký được thêm vào.


2
Cũng lưu ý rằng nếu bạn có một dịch vụ trả lại http có thể vượt quá và bạn sẽ chuyển nó trước khi đăng ký thì bạn chỉ cần hủy đăng ký từ quan sát cuối cùng, không phải là http có thể quan sát được. Trên thực tế, bạn thậm chí sẽ không có Đăng ký để http trực tiếp để bạn không thể.
Simon_Weaver

Mặc dù hủy đăng ký hủy yêu cầu trình duyệt, máy chủ vẫn hành động theo yêu cầu. Có cách nào để thân mật với máy chủ để hủy bỏ hoạt động không? Tôi đang gửi các yêu cầu http liên tiếp, trong đó hầu hết bị hủy bằng cách hủy đăng ký. Nhưng, máy chủ vẫn hoạt động theo các yêu cầu bị hủy bởi khách hàng, khiến yêu cầu hợp pháp phải chờ.
bala

@bala bạn phải đưa ra cơ chế của riêng mình cho việc này - điều này sẽ không liên quan gì đến RxJS. Chẳng hạn, bạn có thể chỉ cần đặt các yêu cầu vào một bảng và chạy chúng sau 5 giây ở chế độ nền, sau đó nếu cần phải hủy một cái gì đó, bạn chỉ cần xóa hoặc đặt cờ trên các hàng cũ sẽ ngăn chúng thực thi. Sẽ hoàn toàn phụ thuộc vào ứng dụng của bạn là gì. Tuy nhiên, vì bạn đề cập đến máy chủ đang chặn, nó có thể được cấu hình để chỉ cho phép một yêu cầu tại một thời điểm - nhưng điều đó một lần nữa sẽ phụ thuộc vào những gì bạn đang sử dụng.
Simon_Weaver

TipTip 2 - là một PRO-TIP, cho bất kỳ ai muốn hủy đăng ký từ nhiều thuê bao bằng cách gọi chỉ một chức năng. Thêm bằng cách sử dụng phương thức .add () và hơn .unubscribe () trong ngDestroy.
abhay tripathi

23

Gọi unsubscribephương thức này là để hủy yêu cầu HTTP đang thực hiện do phương thức này gọi yêu cầu aborttrên đối tượng XHR nằm bên dưới và loại bỏ các trình lắng nghe về các sự kiện tải và lỗi:

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

Điều đó nói rằng, unsubscribeloại bỏ người nghe ... Vì vậy, nó có thể là một ý tưởng tốt nhưng tôi không nghĩ rằng nó cần thiết cho một yêu cầu ;-)

Hy vọng nó sẽ giúp bạn, Thierry


: | đó là vô lý: | Tôi đang tìm cách để ngăn chặn ... nhưng tôi đã tự hỏi, và nếu đó là tôi viết mã, trong một số trường hợp: tôi sẽ làm như thế này: y = x.subscribe((x)=>data = x); sau đó một người dùng thay đổi đầu vào và x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()hey bạn đã sử dụng tài nguyên máy chủ của chúng tôi, tôi đã thắng 'ném bạn đi ..... và trong kịch bản khác: chỉ cần gọi y.stop()ném mọi thứ đi
deadManN

16

Hủy đăng kýPHẢI nếu bạn muốn có hành vi xác định trên tất cả các tốc độ mạng.

Hãy tưởng tượng rằng thành phần A được hiển thị trong một tab - Bạn nhấp vào nút để gửi yêu cầu 'NHẬN'. Phải mất 200 ms để phản hồi trở lại. Vì vậy, bạn an toàn để đóng tab bất cứ lúc nào khi biết rằng, máy sẽ nhanh hơn bạn & phản hồi http được xử lý và hoàn tất trước khi tab bị đóng và thành phần A bị hủy.

Làm thế nào về một mạng rất chậm? Bạn nhấp vào một nút, yêu cầu 'NHẬN' mất 10 giây để nhận được phản hồi của nó, nhưng chờ 5 giây bạn quyết định đóng tab. Điều đó sẽ phá hủy thành phần A để được thu gom rác sau đó. Đợi tí! , chúng tôi đã không hủy đăng ký - bây giờ 5 giây sau, một phản hồi trở lại và logic trong thành phần bị phá hủy sẽ được thực thi. Việc thực hiện đó hiện đang được xem xét out-of-contextvà có thể dẫn đến nhiều thứ bao gồm hiệu suất rất thấp.

Vì vậy, cách tốt nhất là sử dụng takeUntil()và hủy đăng ký các cuộc gọi http khi thành phần bị hủy.

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

Có thể sử dụng BehaviorSubject()thay thế và gọi chỉ complete()trong ngOnDestroy()?
Saksham

Bạn vẫn sẽ cần hủy đăng ký ... tại sao sẽ BehaviourSubjectkhác?
Ben Taliadoros

Không cần hủy đăng ký khỏi các vật quan sát của HTTPClient vì chúng là các vật thể quan sát hữu hạn, tức là chúng đã hoàn thành sau khi phát ra một giá trị.
yatharth varshney

Dù sao thì bạn cũng nên hủy đăng ký, giả sử có lỗi chặn bánh mì nướng, nhưng bạn đã rời khỏi trang gây ra lỗi .. bạn sẽ thấy một thông báo lỗi trong một trang khác .... Ngoài ra, hãy nghĩ về trang kiểm tra sức khỏe gọi tất cả các dịch vụ siêu nhỏ ... vân vân
Washington Guedes

12

Ngoài ra với mô-đun httpClient mới, vẫn giữ nguyên hành vi gói / chung / http / src / jsonp.ts


Đoạn mã trên dường như là từ một bài kiểm tra đơn vị, ngụ ý rằng với HTTPClient, bạn cần tự mình gọi .complete () ( github.com/angular/angular/blob/iêu )
CleverPatrick

Vâng, bạn đúng, nhưng nếu bạn kiểm tra ( github.com/angular/angular/blob/, ) bạn sẽ thấy điều tương tự. Liên quan đến câu hỏi chính, nếu cần thiết để hủy đăng ký rõ ràng? hầu hết các trường hợp không, vì sẽ được xử lý bởi chính Angular. Có thể là một trường hợp trong đó một phản hồi dài có thể xảy ra và bạn đã chuyển sang một tuyến đường khác hoặc có thể phá hủy một thành phần, trong trường hợp đó bạn có khả năng cố gắng truy cập vào một cái gì đó không tồn tại nữa gây ra ngoại lệ.
Ricardo Martínez

8

Bạn không nên hủy đăng ký khỏi các thiết bị quan sát hoàn thành tự động (ví dụ: http, cuộc gọi). Nhưng nó là cần thiết để hủy đăng ký từ quan sát vô hạn như Observable.timer().


6

Sau một thời gian thử nghiệm, đọc tài liệu và mã nguồn của 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/

Unularisty góc: https://blog.angular-university.io/angular-http/

Loại Quan sát cụ thể này là các luồng giá trị đơn: Nếu yêu cầu HTTP thành công, các quan sát này sẽ chỉ phát ra một giá trị và sau đó hoàn tất

Và câu trả lời cho toàn bộ Vấn đề "tôi có CẦN" để hủy đăng ký không?

Nó phụ thuộc. Cuộc gọi http không phải là một vấn đề. Các vấn đề là logic trong các chức năng gọi lại của bạn.

Ví dụ: Định tuyến hoặc Đăng nhập.

Nếu cuộc gọi của bạn là cuộc gọi đăng nhập, bạn không phải "hủy đăng ký" nhưng bạn cần đảm bảo nếu Người dùng rời khỏi trang, bạn sẽ xử lý phản hồi đúng cách nếu không có người dùng.


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

Từ khó chịu đến nguy hiểm

Bây giờ chỉ cần tưởng tượng, mạng chậm hơn bình thường, cuộc gọi mất nhiều thời gian hơn 5 giây và người dùng rời khỏi chế độ xem đăng nhập và chuyển đến "chế độ xem hỗ trợ".

Các thành phần có thể không hoạt động nhưng đăng ký. Trong trường hợp có phản hồi, người dùng sẽ đột nhiên được định tuyến lại (tùy thuộc vào việc triển khai xử lýResponse () của bạn).

Điều này là không tốt.

Cũng chỉ cần tưởng tượng người dùng rời khỏi máy tính, tin rằng anh ta chưa đăng nhập. Nhưng logic của bạn đăng nhập người dùng, bây giờ bạn có một vấn đề bảo mật.

Bạn có thể làm gì mà KHÔNG hủy đăng ký?

Làm cho bạn gọi phụ thuộc vào trạng thái hiện tại của chế độ xem:

  public isActive = false;
  public ngOnInit(): void {
    this.isActive = true;
  }

  public ngOnDestroy(): void {
    this.isActive = false;
  }

Người dùng .pipe(takeWhile(value => this.isActive))để đảm bảo phản hồi chỉ được xử lý khi chế độ xem được kích hoạt.


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

Nhưng làm thế nào bạn có thể chắc chắn rằng thuê bao không gây ra hiện tượng nhớ?

Bạn có thể đăng nhập nếu "torndownLogic" được áp dụng.

RipdownLogic của một thuê bao sẽ được gọi khi đăng ký trống hoặc không đăng ký.


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

Bạn không cần phải hủy đăng ký. Bạn nên biết nếu có vấn đề trong logic của bạn, điều đó có thể gây ra Sự cố trong đăng ký của bạn. Và chăm sóc chúng. Trong hầu hết các trường hợp, nó sẽ không phải là một vấn đề, nhưng đặc biệt ở các nhiệm vụ quan trọng như tự động hóa, bạn nên quan tâm đến hành vi không mong muốn, bằng cách "hủy đăng ký" hoặc logic khác như chức năng gọi lại có điều kiện hoặc đường ống có điều kiện.

Tại sao không luôn luôn hủy đăng ký?

Hãy tưởng tượng bạn thực hiện một yêu cầu đặt hoặc gửi. Máy chủ nhận tin nhắn theo bất kỳ cách nào, chỉ cần phản hồi mất một lúc. Hủy đăng ký, sẽ không hoàn tác bài đăng hoặc đặt. Nhưng khi bạn hủy đăng ký, bạn sẽ không có cơ hội xử lý phản hồi hoặc thông báo cho Người dùng, ví dụ như qua Hộp thoại hoặc Toast / Tin nhắn, v.v.

Việc này khiến Người dùng tin rằng yêu cầu đặt / đăng không được thực hiện.

Vì vậy, nó phụ thuộc. Đó là quyết định thiết kế của bạn, làm thế nào để đối phó với các vấn đề như vậy.


4

Bạn chắc chắn nên đọc này bài viết. Nó cho bạn thấy lý do tại sao bạn phải luôn hủy đăng ký ngay cả từ http .

Nếu sau khi tạo yêu cầu nhưng trước khi nhận được câu trả lời từ phía sau, bạn cho rằng thành phần không cần thiết và hủy nó, đăng ký của bạn sẽ duy trì tham chiếu đến thành phần do đó tạo ra cơ hội gây rò rỉ bộ nhớ.

Cập nhật

Khẳng định trên dường như là đúng, nhưng dù sao, khi câu trả lời trở lại, đăng ký http vẫn bị hủy


1
Có và bạn thực sự sẽ thấy một chữ 'hủy' màu đỏ trong tab mạng của trình duyệt để bạn có thể chắc chắn rằng nó hoạt động.
Simon_Weaver

-2

RxJS có thể quan sát được về cơ bản được liên kết và hoạt động theo đó bạn đăng ký nó. Khi chúng tôi tạo ra có thể quan sát và chuyển động chúng tôi hoàn thành nó, có thể quan sát tự động bị đóng và hủy đăng ký.

Họ làm việc trong cùng một thời trang của người theo dõi nhưng thứ tự khá khác nhau. Thực hành tốt hơn để hủy đăng ký chúng khi thành phần bị phá hủy. Sau đó, chúng tôi có thể làm điều đó bằng cách . cái này. $ ManageSubcrip.unsubscibe ()

Nếu chúng ta đã tạo cú pháp có thể quan sát được như bên dưới, như

** return new Observable ((observer) => {** // Nó có thể quan sát được ở trạng thái lạnh ** observer.complete () **}) **

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.