Sampai saat ini, banyak artikel telah ditulis yang Anda harus berhenti berlangganan dari RxJS yang dapat diobservasi, jika tidak akan terjadi kebocoran memori . Bagi sebagian besar pembaca artikel-artikel semacam itu, peraturan perusahaan "ditandatangani? - ditandatangani!" Namun, sayangnya, sering dalam artikel seperti itu informasi terdistorsi atau sesuatu tidak dinegosiasikan, dan bahkan lebih buruk ketika konsep diganti. Kami akan membicarakan ini.

Ambil contoh artikel ini: https://medium.com/ngx/why-do-you-need-unsubscribe-ee0c62b5d21f

Ketika mereka mengatakan kepada saya tentang "peluang potensial untuk mendapatkan regresi dalam kinerja," saya langsung memikirkan optimasi prematur .


Kami terus membaca artikel oleh pria dengan nama panggilan Reactive Fox :


Selanjutnya ada informasi dan tips yang bermanfaat. Saya setuju bahwa Anda harus selalu berhenti berlangganan dari aliran tak berujung di RxJS . Tapi saya hanya fokus pada informasi yang berbahaya (menurut saya).

Wow ... terjebak dengan kengerian. Intimidasi yang tidak berdasar (tanpa metrik, angka ...) pada saat ini telah mengarah pada fakta bahwa untuk sejumlah besar front-endor, kurangnya berhenti berlangganan seperti kain merah untuk seekor banteng. Ketika mereka menemukan ini, mereka tidak lagi melihat apa pun di sekitar kecuali kain ini.

Penulis artikel itu bahkan membuat aplikasi demo, di mana ia mencoba membuktikan pikirannya:
https://stackblitz.com/edit/why-you-have-to-unsubscribe-from-observable-material
Memang, pada stand-nya Anda dapat melihat bagaimana prosesor bekerja tidak perlu (ketika saya tidak mengklik apa pun) dan bagaimana konsumsi memori meningkat (perubahan kecil):

Sebagai konfirmasi fakta bahwa Anda selalu perlu berhenti berlangganan dari permintaan HttpClient yang Dapat Diobservasi, ia menambahkan interseptor permintaan yang menampilkan "masih hidup ... masih hidup ... masih hidup ..." di konsol:

Yaitu orang menyadap aliran terakhir, menjadikannya tak terbatas (jika terjadi kesalahan, permintaan diulang, tetapi kesalahan selalu terjadi) dan memberikan ini sebagai bukti bahwa Anda harus berhenti berlangganan dari yang terakhir.
StackBlitz tidak terlalu cocok untuk mengukur kinerja aplikasi ada sinkronisasi otomatis selama peningkatan dan membutuhkan sumber daya. Jadi saya membuat aplikasi pengujian saya: https://github.com/andchir/test-angular-app
Ada dua jendela di sana. Ketika Anda membuka masing-masing, permintaan dikirim ke action.php , di mana ada penundaan 3 detik sebagai imitasi dari operasi yang sangat intensif sumber daya. Action.php juga mencatat semua permintaan ke file log.txt .
Kode Action.php<?php header('Content-Type: application/json'); function logging($str, $fileName = 'log.txt') { if (is_array($str)) { $str = json_encode($str); } $rootPath = __DIR__; $logFilePath = $rootPath . DIRECTORY_SEPARATOR . $fileName; $options = [ 'max_log_size' => 200 * 1024 ]; if (!is_dir(dirname($logFilePath))) { mkdir(dirname($logFilePath)); } if (file_exists($logFilePath) && filesize($logFilePath) >= $options['max_log_size']) { unlink($logFilePath); } $fp = fopen( $logFilePath, 'a' ); $dateFormat = 'd/m/YH:i:s'; $str = PHP_EOL . PHP_EOL . date($dateFormat) . PHP_EOL . $str; fwrite( $fp, $str ); fclose( $fp ); return true; } $actionName = isset($_GET['a']) && !is_array($_GET['a']) ? $_GET['a'] : '1'; logging("STARTED-{$actionName}"); sleep(3);
Tapi pertama-tama, terjadi penyimpangan kecil. Pada gambar di bawah (dapat diklik), Anda dapat melihat contoh sederhana tentang cara kerja pengumpul sampah JavaScript di browser Chrome. PUSH terjadi, tetapi setTimeout tidak menghentikan pengumpul sampah dari membersihkan memori.

Jangan lupa untuk memanggil pengumpul sampah dengan satu sentuhan tombol saat Anda bereksperimen.

Mari kembali ke aplikasi pengujian saya. Berikut ini kode untuk kedua windows:
Kode BadModalComponent @Component({ selector: 'app-bad-modal', templateUrl: './bad-modal.component.html', styleUrls: ['./bad-modal.component.css'], providers: [HttpClient] }) export class BadModalComponent implements OnInit, OnDestroy { loading = false; largeData: number[] = (new Array(1000000)).fill(1); destroyed$ = new Subject<void>(); data: DataInterface; constructor( private http: HttpClient, private bsModalRef: BsModalRef ) { } ngOnInit() { this.loadData(); } loadData(): void { // For example only, not for production. this.loading = true; const subscription = this.http.get<DataInterface>('/action.php?a=2').pipe( takeUntil(this.destroyed$), catchError((err) => throwError(err.message)), finalize(() => console.log('FINALIZE')) ) .subscribe({ next: (res) => { setTimeout(() => { console.log(subscription.closed ? 'SUBSCRIPTION IS CLOSED' : 'SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('LOADED'); this.data = res; this.loading = false; }, error: (error) => { setTimeout(() => { console.log(subscription.closed ? 'ERROR - SUBSCRIPTION IS CLOSED' : 'ERROR - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('ERROR', error); }, complete: () => { setTimeout(() => { console.log(subscription.closed ? 'COMPLETED - SUBSCRIPTION IS CLOSED' : 'COMPLETED - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('COMPLETED'); } }); } close(event?: MouseEvent): void { if (event) { event.preventDefault(); } this.bsModalRef.hide(); } ngOnDestroy() { console.log('DESTROY'); this.destroyed$.next(); this.destroyed$.complete(); } }
Seperti yang Anda lihat, ada yang berhenti berlangganan (takeUntil). Segalanya seperti yang disarankan oleh "guru" itu. Ada juga array yang besar.
GoodModalComponent Code @Component({ selector: 'app-good-modal', templateUrl: './good-modal.component.html', styleUrls: ['./good-modal.component.css'] }) export class GoodModalComponent implements OnInit, OnDestroy { loading = false; largeData: number[] = (new Array(1000000)).fill(1); data: DataInterface; constructor( private http: HttpClient, private bsModalRef: BsModalRef ) { } ngOnInit() { this.loadData(); } loadData(): void { // For example only, not for production. this.loading = true; const subscription = this.http.get<DataInterface>('/action.php?a=1').pipe( catchError((err) => throwError(err.message)), finalize(() => console.log('FINALIZE')) ) .subscribe({ next: (res) => { setTimeout(() => { console.log(subscription.closed ? 'SUBSCRIPTION IS CLOSED' : 'SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('LOADED'); this.data = res; this.loading = false; }, error: (error) => { setTimeout(() => { console.log(subscription.closed ? 'ERROR - SUBSCRIPTION IS CLOSED' : 'ERROR - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('ERROR', error); }, complete: () => { setTimeout(() => { console.log(subscription.closed ? 'COMPLETED - SUBSCRIPTION IS CLOSED' : 'COMPLETED - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('COMPLETED'); } }); } close(event?: MouseEvent): void { if (event) { event.preventDefault(); } this.bsModalRef.hide(); } ngOnDestroy() { console.log('DESTROY'); } }
Ada properti yang sama persis dengan array besar, tetapi tidak ada berhenti berlangganan. Dan ini tidak menghentikan saya untuk menyebut jendela ini jendela yang bagus. Kenapa - nanti.
Tonton videonya:
Seperti yang Anda lihat, dalam kedua kasus, setelah beralih ke komponen kedua, pengumpul sampah berhasil mengembalikan memori ke nilai normal. Ya, orang dapat memungkinkannya untuk menghapus memori juga setelah menutup jendela, tetapi dalam percobaan kami ini tidak penting. Ternyata "guru" itu salah ketika dia berkata:
Misalnya, Anda mengajukan permintaan, tetapi ketika jawabannya belum tiba dari backend, Anda akan menghancurkan komponen itu sebagai tidak perlu, maka langganan Anda akan menyimpan tautan ke komponen tersebut , sehingga berpotensi menyebabkan kebocoran memori.
Ya, dia berbicara tentang kebocoran "potensial" . Tapi, jika alirannya terbatas, maka tidak akan ada kebocoran memori.
Saya melihat seruan marah dari "guru" tersebut. Mereka pasti akan memberi tahu kami sesuatu seperti: "ok, tidak ada kebocoran memori, tetapi dengan berhenti berlangganan kami juga membatalkan permintaan , yang berarti kami akan yakin bahwa tidak ada kode yang akan dieksekusi lagi setelah menerima respons dari server . " Pertama, saya tidak mengatakan bahwa berhenti berlangganan selalu buruk, saya hanya mengatakan bahwa Anda mengganti konsep . Ya, fakta bahwa setelah jawabannya tiba beberapa operasi yang lebih tidak berguna akan dilakukan adalah buruk, tetapi Anda hanya dapat melindungi diri dari kebocoran memori nyata dengan berhenti berlangganan (dalam hal ini), dan Anda dapat melindungi diri dari efek lain yang tidak diinginkan dengan cara lain . Tidak perlu mengintimidasi pembaca dan memaksakan gaya penulisan kode mereka sendiri pada mereka.
Apakah kita selalu perlu membatalkan permintaan jika pengguna berubah pikiran? Tidak selalu! Jangan lupa bahwa Anda membatalkan permintaan, tetapi Anda tidak membatalkan operasi di server . Bayangkan bahwa seorang pengguna telah membuka komponen, sesuatu telah dimuat untuk waktu yang lama dan dia beralih ke komponen lain. Mungkin saja server dimuat dan tidak mengatasi semua permintaan dan operasi. Dalam hal ini, pengguna dapat dengan cepat menyodok semua tautan di navigasi dan membuat beban yang lebih besar di server , karena permintaan tidak berhenti di sisi server (dalam kebanyakan kasus).
Tonton video berikut:
Saya membuat pengguna menunggu jawaban. Dalam kebanyakan kasus, jawabannya akan datang dengan cepat dan pengguna tidak akan mengalami ketidaknyamanan. Tetapi dengan cara ini, kami akan menyelamatkan server dari melakukan operasi berat yang berulang, jika ada.
Ringkasan:
- Saya tidak mengatakan bahwa Anda tidak perlu berhenti berlangganan dari permintaan HttpClient RxJS. Saya hanya mengatakan bahwa ada saat-saat ini tidak perlu. Tidak perlu mengganti konsep. Jika Anda berbicara tentang kebocoran memori, tunjukkan kebocoran ini. Bukan console.log Anda yang tak ada habisnya, yaitu kebocoran. Apa memori diukur? Apa waktu operasi diukur? Inilah yang perlu ditunjukkan.
- Saya tidak menyebut solusi saya, yang saya terapkan dalam aplikasi tes, "peluru perak". Sebaliknya, saya mendesak fronttender untuk diberi lebih banyak kebebasan. Biarkan dia memutuskan bagaimana menulis kodenya. Tidak perlu mengintimidasi dia dan memaksakan gaya perkembangannya sendiri.
- Saya menentang fanatisme dan optimisasi prematur. Saya sudah terlalu banyak melihat ini belakangan ini.
- Browser memiliki metode yang lebih canggih untuk menemukan kebocoran memori daripada yang saya tunjukkan. Saya pikir dalam kasus saya penerapan metode sederhana ini sudah cukup. Tapi saya sarankan Anda membiasakan diri dengan topik secara lebih rinci, misalnya, dalam artikel ini: https://habr.com/en/post/309318/ .
UPD # 1
Saat ini, pos merosot selama hampir satu hari. Awalnya dia pergi ke pro dan kontra, kemudian penilaian berhenti di nol. Ini berarti bahwa audiensi dibagi persis menjadi dua kubu. Saya tidak tahu apakah ini baik atau buruk.
UPD # 2
Dalam komentar, Jet Fox muncul (penulis artikel sedang dianalisis). Awalnya dia mengucapkan terima kasih, dia sangat sopan. Tetapi, melihat kepasifan penonton, dia mulai menekan. Sampai pada titik ia menulis bahwa saya harus meminta maaf. Yaitu dia berbohong (berbaring dengan bingkai kuning di atas), dan saya harus minta maaf.
Pada awalnya saya berpikir bahwa pencegat aliran dengan pengulangan tak berujung (well, 2-3 pengulangan), yang ia tulis dalam aplikasi demo-nya, hanya untuk tes dan memberi informasi. Tetapi ternyata dia menganggapnya sebagai contoh dari kehidupan. Yaitu untuk memblokir tombol jendela - tidak mungkin . Dan untuk menciptakan pencegat seperti itu, melanggar prinsip-prinsip SOLID, melanggar modularitas aplikasi (modul dan komponen harus independen satu sama lain), membiarkan tes unit unit Anda (komponen, layanan) melalui hutan - Anda bisa. Bayangkan situasinya: Anda menulis sebuah komponen, menulis unit test untuknya. Dan kemudian Fox seperti itu muncul, menambahkan pencegat serupa ke aplikasi Anda, dan tes Anda menjadi tidak berguna. Lalu dia masih berkata kepada Anda, "Mengapa Anda tidak memperkirakan bahwa saya mungkin ingin menambahkan pencegat seperti itu. Ya, perbaiki kode Anda." Mungkin ini mungkin menjadi kenyataan di timnya, tapi saya tidak berpikir ini harus didorong atau ditutup-tutupi.
UPD # 3
Komentar sebagian besar membahas langganan dan berhenti berlangganan. Apakah posting disebut "Berhenti Berlangganan Jahat"? Tidak. Saya tidak menyarankan Anda untuk berhenti berlangganan. Lakukan seperti yang Anda lakukan sebelumnya. Tetapi Anda harus mengerti mengapa Anda melakukan ini. Berhenti berlangganan bukan optimasi prematur. Tetapi, dengan melangkah di jalur perlindungan terhadap potensi ancaman (sebagaimana penulis artikel di bawah analisis memanggil kami), Anda dapat melewati batas. Kemudian kode Anda bisa menjadi kelebihan beban dan sulit untuk dipelihara.
Artikel ini adalah tentang fanatisme, yang mengarah pada distribusi informasi yang tidak diverifikasi. Dalam beberapa kasus, perlu dikaitkan dengan tidak adanya berhenti berlangganan secara lebih tenang (Anda perlu memahami dengan jelas apakah ada masalah dalam kasus tertentu).
UPD # 4
Sebaliknya, saya mendesak fronttender untuk diberi lebih banyak kebebasan. Biarkan dia memutuskan bagaimana menulis kodenya.
Di sini Anda perlu mengklarifikasi. Saya untuk standar. Tetapi standar dapat ditetapkan oleh penulis perpustakaan atau timnya, sementara ini tidak (dalam dokumentasi dan secara resmi). Sebagai contoh, dokumentasi kerangka kerja Symfony memiliki bagian Praktik terbaik . Jika itu sama dalam dokumentasi RxJS dan itu akan mengatakan "ditandatangani-berhenti berlangganan", saya tidak akan memiliki keinginan untuk berdebat dengannya.
UPD # 5
Komentar penting dengan jawaban dari orang-orang terkemuka:
https://habr.com/en/post/479732/#comment_21012620
Rekomendasi untuk memenuhi kontrak "ditandatangani-berhenti berlangganan" dari pengembang RxJS ada , tetapi secara tidak resmi.