8000 fix(common): cleanup `updateLatestValue` if view is destroyed before promise resolves [patch] by arturovt · Pull Request #61064 · angular/angular · GitHub
[go: up one dir, main page]

Skip to content

fix(common): cleanup updateLatestValue if view is destroyed before promise resolves [patch] #61064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions packages/common/src/pipes/async_pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ɵisPromise,
ɵisSubscribable,
} from '@angular/core';
import {Observable, Subscribable, Unsubscribable} from 'rxjs';
import type {Observable, Subscribable, Unsubscribable} from 'rxjs';

import {invalidPipeArgumentError} from './invalid_pipe_argument_error';

Expand Down Expand Up @@ -54,13 +54,49 @@ class SubscribableStrategy implements SubscriptionStrategy {
}

class PromiseStrategy implements SubscriptionStrategy {
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): Promise<any> {
return async.then(updateLatestValue, (e) => {
throw e;
});
createSubscription(
async: Promise<any>,
updateLatestValue: ((v: any) => any) | null,
): Unsubscribable {
// According to the promise specification, promises are not cancellable by default.
// Once a promise is created, it will either resolve or reject, and it doesn't
// provide a built-in mechanism to cancel it.
// There may be situations where a promise is provided, and it either resolves after
// the pipe has been destroyed or never resolves at all. If the promise never
// resolves — potentially due to factors beyond our control, such as third-party
// libraries — this can lead to a memory leak.
// When we use `async.then(updateLatestValue)`, the engine captures a reference to the
// `updateLatestValue` function. This allows the promise to invoke that function when it
// resolves. In this case, the promise directly captures a reference to the
// `updateLatestValue` function. If the promise resolves later, it retains a reference
// to the original `updateLatestValue`, meaning that even if the context where
// `updateLatestValue` was defined has been destroyed, the function reference remains in memory.
// This can lead to memory leaks if `updateLatestValue` is no longer needed or if it holds
// onto resources that should be released.
// When we do `async.then(v => ...)` the promise captures a reference to the lambda
// function (the arrow function).
// When we assign `updateLatestValue = null` within the context of an `unsubscribe` function,
// we're changing the reference of `updateLatestValue` in the current scope to `null`.
// The lambda will no longer have access to it after the assignment, effectively
// preventing any further calls to the original function and allowing it to be garbage collected.
async.then(
// Using optional chaining because we may have set it to `null`; since the promise
// is async, the view might be destroyed by the time the promise resolves.
(v) => updateLatestValue?.(v),
(e) => {
throw e;
},
);
return {
unsubscribe: () => {
updateLatestValue = null;
},
};
}

dispose(subscription: Promise<any>): void {}
dispose(subscription: Unsubscribable): void {
subscription.unsubscribe();
}
}

const _promiseStrategy = new PromiseStrategy();
Expand Down
0