Skip to content
Closed
Show file tree
Hide file tree
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
27 changes: 18 additions & 9 deletions packages/zone.js/lib/common/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,31 @@ export function patchFetch(Zone: ZoneType): void {
options.signal = fetchSignal;
args[1] = options;

let onAbort: () => void;
if (signal) {
const nativeAddEventListener =
signal[Zone.__symbol__('addEventListener') as 'addEventListener'] ||
signal.addEventListener;

nativeAddEventListener.call(
signal,
'abort',
function () {
ac!.abort();
},
{once: true},
);
onAbort = () => ac!.abort();
nativeAddEventListener.call(signal, 'abort', onAbort, {once: true});
}

return createFetchTask('fetch', {fetchArgs: args} as FetchTaskData, fetch, this, args, ac);
return createFetchTask(
'fetch',
{fetchArgs: args} as FetchTaskData,
fetch,
this,
args,
ac,
).finally(() => {
// We need to be good citizens and remove the `abort` listener once
// the fetch is settled. The `abort` listener may not be called at all,
// which means the event listener closure would retain a reference to
// the `ac` object even if it goes out of scope. Since browser's garbage
// collectors work differently, some may not be smart enough to collect a signal.
signal?.removeEventListener('abort', onAbort);
});
};

if (OriginalResponse?.prototype) {
Expand Down
9 changes: 9 additions & 0 deletions packages/zone.js/test/common/fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ describe(
'invokeTask:fetch:macroTask',
'scheduleTask:Promise.then:microTask',
'invokeTask:Promise.then:microTask',

// This is the `finally` task, which is used for cleanup.
'scheduleTask:Promise.then:microTask',
'invokeTask:Promise.then:microTask',
]);
done();
},
Expand All @@ -194,6 +198,11 @@ describe(
'invokeTask:fetch:macroTask',
'scheduleTask:Promise.then:microTask',
'invokeTask:Promise.then:microTask',

// This is the `finally` task, which is used for cleanup.
'scheduleTask:Promise.then:microTask',
'invokeTask:Promise.then:microTask',

// Please refer to the issue link above. Previously, `Response` methods were not
// patched by zone.js, and their return values were considered only as
// microtasks (not macrotasks). The Angular zone stabilized prematurely,
Expand Down