@@ -16,7 +16,7 @@ import {
1616 ɵisPromise ,
1717 ɵisSubscribable ,
1818} from '@angular/core' ;
19- import { Observable , Subscribable , Unsubscribable } from 'rxjs' ;
19+ import type { Observable , Subscribable , Unsubscribable } from 'rxjs' ;
2020
2121import { invalidPipeArgumentError } from './invalid_pipe_argument_error' ;
2222
@@ -54,13 +54,49 @@ class SubscribableStrategy implements SubscriptionStrategy {
5454}
5555
5656class PromiseStrategy implements SubscriptionStrategy {
57- createSubscription ( async : Promise < any > , updateLatestValue : ( v : any ) => any ) : Promise < any > {
58- return async . then ( updateLatestValue , ( e ) => {
59- throw e ;
60- } ) ;
57+ createSubscription (
58+ async : Promise < any > ,
59+ updateLatestValue : ( ( v : any ) => any ) | null ,
60+ ) : Unsubscribable {
61+ // According to the promise specification, promises are not cancellable by default.
62+ // Once a promise is created, it will either resolve or reject, and it doesn't
63+ // provide a built-in mechanism to cancel it.
64+ // There may be situations where a promise is provided, and it either resolves after
65+ // the pipe has been destroyed or never resolves at all. If the promise never
66+ // resolves — potentially due to factors beyond our control, such as third-party
67+ // libraries — this can lead to a memory leak.
68+ // When we use `async.then(updateLatestValue)`, the engine captures a reference to the
69+ // `updateLatestValue` function. This allows the promise to invoke that function when it
70+ // resolves. In this case, the promise directly captures a reference to the
71+ // `updateLatestValue` function. If the promise resolves later, it retains a reference
72+ // to the original `updateLatestValue`, meaning that even if the context where
73+ // `updateLatestValue` was defined has been destroyed, the function reference remains in memory.
74+ // This can lead to memory leaks if `updateLatestValue` is no longer needed or if it holds
75+ // onto resources that should be released.
76+ // When we do `async.then(v => ...)` the promise captures a reference to the lambda
77+ // function (the arrow function).
78+ // When we assign `updateLatestValue = null` within the context of an `unsubscribe` function,
79+ // we're changing the reference of `updateLatestValue` in the current scope to `null`.
80+ // The lambda will no longer have access to it after the assignment, effectively
81+ // preventing any further calls to the original function and allowing it to be garbage collected.
82+ async . then (
83+ // Using optional chaining because we may have set it to `null`; since the promise
84+ // is async, the view might be destroyed by the time the promise resolves.
85+ ( v ) => updateLatestValue ?.( v ) ,
86+ ( e ) => {
87+ throw e ;
88+ } ,
89+ ) ;
90+ return {
91+ unsubscribe : ( ) => {
92+ updateLatestValue = null ;
93+ } ,
94+ } ;
6195 }
6296
63- dispose ( subscription : Promise < any > ) : void { }
97+ dispose ( subscription : Unsubscribable ) : void {
98+ subscription . unsubscribe ( ) ;
99+ }
64100}
65101
66102const _promiseStrategy = new PromiseStrategy ( ) ;
0 commit comments