| 59 | } |
| 60 | |
| 61 | class PromiseStrategy implements SubscriptionStrategy { |
| 62 | createSubscription( |
| 63 | async: PromiseLike<any>, |
| 64 | updateLatestValue: ((v: any) => any) | null, |
| 65 | onError: ((e: unknown) => void) | null, |
| 66 | ): Unsubscribable { |
| 67 | // According to the promise specification, promises are not cancellable by default. |
| 68 | // Once a promise is created, it will either resolve or reject, and it doesn't |
| 69 | // provide a built-in mechanism to cancel it. |
| 70 | // There may be situations where a promise is provided, and it either resolves after |
| 71 | // the pipe has been destroyed or never resolves at all. If the promise never |
| 72 | // resolves — potentially due to factors beyond our control, such as third-party |
| 73 | // libraries — this can lead to a memory leak. |
| 74 | // When we use `async.then(updateLatestValue)`, the engine captures a reference to the |
| 75 | // `updateLatestValue` function. This allows the promise to invoke that function when it |
| 76 | // resolves. In this case, the promise directly captures a reference to the |
| 77 | // `updateLatestValue` function. If the promise resolves later, it retains a reference |
| 78 | // to the original `updateLatestValue`, meaning that even if the context where |
| 79 | // `updateLatestValue` was defined has been destroyed, the function reference remains in memory. |
| 80 | // This can lead to memory leaks if `updateLatestValue` is no longer needed or if it holds |
| 81 | // onto resources that should be released. |
| 82 | // When we do `async.then(v => ...)` the promise captures a reference to the lambda |
| 83 | // function (the arrow function). |
| 84 | // When we assign `updateLatestValue = null` within the context of an `unsubscribe` function, |
| 85 | // we're changing the reference of `updateLatestValue` in the current scope to `null`. |
| 86 | // The lambda will no longer have access to it after the assignment, effectively |
| 87 | // preventing any further calls to the original function and allowing it to be garbage collected. |
| 88 | async.then( |
| 89 | // Using optional chaining because we may have set it to `null`; since the promise |
| 90 | // is async, the view might be destroyed by the time the promise resolves. |
| 91 | (v) => updateLatestValue?.(v), |
| 92 | (e) => onError?.(e), |
| 93 | ); |
| 94 | return { |
| 95 | unsubscribe: () => { |
| 96 | updateLatestValue = null; |
| 97 | onError = null; |
| 98 | }, |
| 99 | }; |
| 100 | } |
| 101 | |
| 102 | dispose(subscription: Unsubscribable): void { |
| 103 | subscription.unsubscribe(); |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | const _promiseStrategy = new PromiseStrategy(); |
| 108 | const _subscribableStrategy = new SubscribableStrategy(); |
nothing calls this directly
no outgoing calls
no test coverage detected